Skip to main content
Glama
davidmrguo

tabulite-mcp

by davidmrguo

Tabulite MCP

Анализируйте CSV-файлы, которые слишком велики для электронных таблиц — и слишком велики, чтобы вставить их в чат, — предоставив вашему ИИ-ассистенту локальный рантайм SQLite вместо самих данных.

Tabulite MCP — сокращённо Tabulite — это локальный MCP сервер. Укажите ему папку с CSV-файлами, и ваш десктопный ИИ-клиент сможет импортировать их в SQLite, изучить, что на самом деле содержат колонки, и отвечать на вопросы, составляя SQL — при этом ни одна строка ваших данных не покинет вашу машину и не попадёт в диалог.

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

Внутри сервера нет LLM. Мышление выполняет ваш ИИ-клиент; Tabulite даёт ему метаданные для размышлений, интерфейс SQL только для чтения для исследования и прямой путь к диску, когда ответ — это набор данных, а не предложение.


Зачем

Спросите ИИ о CSV-файле на 500 МБ — и все варианты плохие: вставить образец и потерять ответ, загрузить весь файл и сжечь контекстное окно (а заодно отправить свои данные неизвестно куда) или пойти писать скрипт самостоятельно.

Одна машина и один SQLite-файл справляются с таким объёмом без малейших усилий. Tabulite помещает этот рантайм рядом с данными и открывает к нему доступ через MCP. Ваш ассистент читает несколько сотен токенов профилей колонок, пишет 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.csv, source/customers.csv), так что вы можете попробовать сразу. Подключите ваш ИИ-клиент (ниже) и спросите:

«Проанализируй 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;

Подключите вашего ИИ-клиента

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/.


Инструменты, которые получает ваш ИИ

Tool

What it does

list_sources()

CSV-файлы в source/, с размерами и статусом импорта

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. Поэтому хранилище сохраняет то, что было в файле, а интерпретация происходит позже, там, где её видно и можно отменить.

Профили объясняют ИИ, что означают колонки

После импорта каждая колонка профилируется, и результат сохраняется в 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, разбираются как этот тип. Профили — это свидетельства для ИИ, а не инструкция слою хранения: ваши импортированные данные никогда не переписываются, чтобы соответствовать догадке.

Функции TRY_* вместо CAST

CAST в SQLite опасен своей вседозволенностью:

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

AVG() по колонке с несколькими тысячами значений 'unknown' молча усредняет их как нули. Поэтому Tabulite регистрирует строгие преобразования на каждом подключении:

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

Также доступны: TRY_INTEGER, TRY_DATE, TRY_DATETIME, TRY_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 value

Stored as

125.40

"125.40"

(empty)

NULL

N/A

NULL

unknown

"unknown"

-

"-"

Маркеры по умолчанию: пустая строка, NULL, null, N/A, NA. «Поле было пустым» и «поле содержало мусор» — это разные результаты, и если схлопнуть их при импорте, можно спрятать проблему качества данных, которую стоит увидеть.

Файлы идентифицируются по содержимому, а не по имени

Переименуйте sales.csv в sales_FINAL_v2.csv и импортируйте снова — Tabulite узнает содержимое и переиспользует существующую таблицу вместо дублирования. Идентичность — это SHA-256 файла, вычисляемый во время импорта, а не отдельным чтением. Измените один байт — и это уже новый источник со своей таблицей.

Всё импортированное живёт в одной базе данных (workspace/databases/main.sqlite), поэтому ваш ассистент может соединять данные из разных файлов обычным SQL. Когда два файла претендуют на одно и то же имя таблицы — например, sales.csv в двух разных папках, — второй получает суффикс от своего собственного хеша содержимого (sales и sales_4b11d3). Это означает, что конкретный файл всегда попадает в одно и то же имя таблицы независимо от порядка импорта.

Большие результаты сохраняются на диск, а не в чат

query_sql() возвращает не более 1 000 строк и всегда сообщает об этом ("truncated": true) — это намёк агрегировать в SQL, а не подгружать большой результат в диалог постранично. Патологические запросы — случайное декартово произведение, безграничная рекурсивная CTE — отменяются по таймауту.

Когда пользователю действительно нужны строки, export_query() выполняет тот же SQL только для чтения без ограничения по строкам и потоково записывает курсор прямо в файл внутри workspace/exports/:

«Дай мне все email-транзакции за 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 строк, и для 5 миллионов.


Безопасность

Ваши CSV-файлы никогда не изменяются. source/ монтируется только для чтения на уровне Docker. Всё записываемое попадает в workspace/.

Каждый сгенерированный ИИ запрос — только для чтения, это обеспечивается на четырёх уровнях:

  1. подключение открывается как file:…?mode=ro, поэтому операционная система держит файл доступным только для чтения;

  2. PRAGMA query_only=ON заставляет сам SQLite отказывать в записи через этот дескриптор;

  3. загрузка расширений явно отключена;

  4. callback set_authorizer() разрешает только SQLITE_SELECT, SQLITE_READ, SQLITE_FUNCTION (кроме встроенных функций, обращающихся к файловой системе) и SQLITE_RECURSIVE, запрещая всё остальное — запись, изменение схемы, ATTACH/DETACH, любые PRAGMA, управление транзакциями, обслуживание.

Уровень 4 — это настоящий механизм: он выполняется внутри SQLite во время подготовки выражения и оценивает, что запрос делает, а не как записан его текст. Перед ним, как эшелонированная защита, стоит SQL-скраббер — а также чтобы модель получала понятную ошибку (only read-only statements are allowed; found 'DROP') вместо голого not authorized.

Это различие работает в обе стороны, и набор тестов это закрепляет: CASE … END и скалярная функция replace() — это обычный аналитический SQL, они продолжат работать, а REPLACE INTo, PRAGMA writable_schema = ON и load_extension() отклоняются.

Пути ограничены. Сервер читает только внури source/ и пишет только внури workspace/exports/. Обход путей (../), абсолтные пути и симлинки, ведущие за пределы проекта, отклоняются; имена экспортных файлов санируются, а существующий экспорт никогда не перезаписывается.

Никакой аутентификации — так задумано. Контейнер публикуется только на 127.0.0.1 и предназначен для клиента на той же машине. Не открывайте его в сеть.


Конфигурация

Всё опционально; задаётся в compose.yaml.

Variable

Default

What it controls

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-rebinding)


Структура проекта

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 (API v2: MCPServer, host/port в run()). Никаких pandas, NumPy и ORM — ядро написано на узнаваемо обычном Python: sqlite3.connect(), conn.executemany(), conn.create_function(), cursor.fetchmany().

Масштаб: CSV размером 133 МБ / 2 000 000 строк импортируется и профилируется примерно за две минуты при стабильном потреблении памяти контейнера около 100 МБ; агрегация по нему занимает пару секунд. Импорт ограничен диском, а не ОЗУ.


Устранение неполадок

Порт 8000 уже занят — измените сторону хоста в пробросе портов в compose.yaml ("127.0.0.1:8001:8000") и укажите вашему клиенту новый порт.

Клиент не может подключиться — проверьте, что сервер запущен, с помощью curl http://localhost:8000/health, затем docker compose logs -f.

Ошибки прав при записи в workspace/ (Linux) — раскомментируйте строку user: в compose.yaml, чтобы файлы создавались от вашего имени, а не от имени пользователя контейнера.

Файл в source/ не отображается — обнаруживаются только .csv и .tsv, а скрытые файлы пропускаются.

"unknown table" после редактирования 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