tabulite-mcp
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 |
| CSV-файлы в |
| колонки, разделитель и несколько примеров строк — без импорта |
| потоково импортирует CSV в SQLite и профилирует его |
| импортированные таблицы с количеством строк и источником |
| компактный профиль каждой колонки |
| полные детали по одной колонке с примерами |
| несколько строк, чтобы увидеть, как выглядят данные |
| аналитический SQL только для чтения (не более 1 000 строк) |
| полный результат, потоково записываемый в файл |
Заметно отсутствует всё предметно-специфичное. Нет ни 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_REALprofile_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 wrongAVG() по колонке с несколькими тысячами значений '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 |
|
|
(empty) |
|
|
|
|
|
|
|
Маркеры по умолчанию: пустая строка, 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/.
Каждый сгенерированный ИИ запрос — только для чтения, это обеспечивается на четырёх уровнях:
подключение открывается как
file:…?mode=ro, поэтому операционная система держит файл доступным только для чтения;PRAGMA query_only=ONзаставляет сам SQLite отказывать в записи через этот дескриптор;загрузка расширений явно отключена;
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 |
|
| каталог источника только для чтения |
|
| рабочая область для записи |
|
| значения, импортируемые как SQL NULL |
|
| лимит строк для интерактивных запросов |
|
| секунд до отмены запроса |
|
| секунд до отмены экспорта |
|
| строк на один |
|
| адрес привязки внутри контейнера |
| 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Запуск тестов:
pytest211 тестов покрывают обнаружение источников и отклонение обхода путей, потоковый импорт, обработку 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, без передачи авторских прав). Авторские права остаются у тех, кто написал код, и это осознанное решение: проект должен оставаться проектом с открытым исходным кодом, а не становиться чьим-то продуктом.
This server cannot be installed
Maintenance
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
- FlicenseNot gradedqualityDmaintenanceAI-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
- FlicenseBqualityDmaintenanceEnables Claude to directly access, query, and analyze local CSV files using natural language, keeping data private and local.41
- FlicenseNot gradedqualityDmaintenanceEnables 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
- AlicenseNot gradedqualityDmaintenanceEnables querying Excel and CSV files using SQL via natural language, allowing AI assistants to analyze data without manual SQL writing.1MIT
Related MCP Connectors
Explore, query, and inspect SQLite databases with ease. List tables, preview results, and view det…
Connect AI assistants to your GitHub-hosted Obsidian vault to seamlessly access, search, and analy…
Query PostgreSQL databases in plain English — LLM-generated, safety-validated SQL.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
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