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

Related MCP Connectors

Related MCP Servers

  • 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.
    4
    -
  • A
    license
    Not graded
    quality
    A
    maintenance
    Enables AI agents to run SQL analytics directly on CSV files in place via MCP, avoiding imports and token-heavy data pasting. It provides read-only querying, schema inspection, and file listing while keeping data local.
    30
    MIT