Skip to main content
Glama
davidmrguo

tabulite-mcp

by davidmrguo

Tabulite MCP

Analiza archivos CSV demasiado grandes para una hoja de cálculo — y demasiado grandes para pegarlos en un chat — dándole a tu asistente de IA un runtime SQLite local en lugar de los datos.

Tabulite MCP — Tabulite, para abreviar — es un servidor MCP local. Apunta el servidor a una carpeta de archivos CSV y tu cliente de IA de escritorio podrá importarlos en SQLite, inspeccionar qué contienen realmente las columnas y responder preguntas escribiendo SQL — sin que una sola fila de tus datos salga de tu máquina ni entre en la conversación.

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

No hay ningún LLM dentro del servidor. Tu cliente de IA es quien piensa; Tabulite le da metadatos sobre los que pensar, una interfaz SQL de solo lectura con la que explorar y un camino directo al disco cuando la respuesta es un conjunto de datos en lugar de una frase.


Por qué

Pregúntale a una IA por un CSV de 500 MB y tienes malas opciones: pegar una muestra y perder la respuesta, subir el archivo entero y quemar tu ventana de contexto (además de enviar tus datos a algún sitio), o ponerte a escribir un script tú mismo.

Una sola máquina y un solo archivo SQLite manejan este tamaño sin despeinarse. Tabulite coloca ese runtime junto a los datos y lo expone a través de MCP. Tu asistente lee unos cientos de tokens de perfiles de columna, escribe el SQL y recibe agregados. Las filas permanecen en el disco.

Ideal para: análisis puntuales de exportaciones CSV, volcados de logs y extractos en tu propio portátil — archivos que se le quedaron pequeños a Excel, pero que siguen teniendo cabida en una sola máquina. No es adecuado: pipelines de producción, ETL programado, acceso multiusuario o cualquier cosa que pertenezca a un almacén de datos real.


Related MCP server: csv-mcp-server

Inicio rápido

Requisitos: Docker Desktop (o Docker Engine + Compose). Nada más: no necesitas configurar Python.

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

El servidor está ahora en http://localhost:8000/mcp, con una comprobación de salud en http://localhost:8000/health.

Dos CSV de ejemplo pequeños (source/sales.csv, source/customers.csv) se incluyen con el repositorio para que puedas probarlo de inmediato. Conecta tu cliente de IA (más abajo) y luego pregunta:

"Analiza sales.csv. ¿Qué canal generó más ingresos?"

Tu asistente llamará a list_sources(), import_source("sales.csv"), profile_table("sales") y luego escribirá algo como:

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;

Conecta tu cliente de IA

Claude Code

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

Cualquier cliente con configuración JSON (Claude Desktop, Cursor y similares):

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

Clientes que solo hablan stdio: pon un puente como mcp-remote delante de la URL.

Usa tus propios datos

Coloca archivos CSV en source/ — eso es todo, no hace falta reiniciar:

cp ~/Downloads/huge_export.csv source/

Tus archivos están montados en solo lectura y están en gitignore, por lo que nunca se confirman y el servidor nunca puede modificarlos. Todo lo que Tabulite crea (bases de datos, exportaciones) va a parar a workspace/.


Las herramientas que recibe tu IA

Tool

Qué hace

list_sources()

Archivos CSV en source/, con tamaños y estado de importación

inspect_source(path)

columnas, delimitador y algunas filas de muestra — sin importar

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

transmitir un CSV a SQLite y perfilarlo

list_tables()

tablas importadas con recuentos de filas y su origen

profile_table(table_name, refresh?)

perfil compacto de cada columna

profile_column(table_name, column_name)

detalle completo de una columna, con ejemplos

sample_table(table_name, limit=20)

algunas filas, para ver cómo son los datos

query_sql(sql)

SQL analítico de solo lectura (máximo 1 000 filas)

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

resultado completo transmitido a un archivo

Brilla por su ausencia cualquier cosa específica del dominio. No hay top_products() ni calculate_revenue(). Tu asistente escribe el SQL, que es precisamente el objetivo: puede responder preguntas que nadie previó.


Cómo funciona

Los campos CSV se almacenan como TEXT, a propósito

Toda columna importada es TEXT:

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

Adivinar los tipos en el momento de la importación destruye datos antes de que nadie los haya mirado: "1,234" se convierte en 1, un código de producto con cero inicial se convierte en un entero, "2025-13-40" se convierte silenciosamente en NULL. Por eso el almacenamiento conserva lo que decía el archivo, y la interpretación ocurre después, donde es visible y reversible.

Los perfiles le dicen a la IA qué significan las columnas

Después de la importación, se perfila cada columna y el resultado se guarda en workspace/catalog.sqlite. Esta es la salida real para la muestra incluida:

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") va más allá y muestra los auténticos culpables: invalid_examples: ["pending", "unknown"].

La inferencia es conservadora: solo se asigna un tipo cuando ≥99% de los valores no nulos se interpretan como tal. Los perfiles son evidencia para la IA, nunca una instrucción para la capa de almacenamiento: tus datos importados nunca se reescriben para ajustarse a una suposición.

Las funciones TRY_* en lugar de CAST

El CAST de SQLite es peligrosamente permisivo:

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

Un AVG() sobre una columna con unos miles de valores 'unknown' promedia ceros silenciosamente. Por eso Tabulite registra conversiones estrictas en cada conexión:

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

También disponibles: TRY_INTEGER, TRY_DATE, TRY_DATETIME, TRY_BOOLEAN. Como los agregados de SQLite omiten NULL, los valores malos quedan excluidos en lugar de contarse como cero — y tu asistente puede comprobar el denominador:

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

Los datos faltantes y los inválidos siguen siendo distinguibles

Solo los marcadores de valor ausente configurados se convierten en NULL de SQL. Los valores que simplemente no se pueden interpretar se conservan exactamente como estaban escritos:

Valor CSV

Se almacena como

125.40

"125.40"

(vacío)

NULL

N/A

NULL

unknown

"unknown"

-

"-"

Marcadores predeterminados: cadena vacía, NULL, null, N/A, NA. «Este campo estaba en blanco» y «este campo contenía basura» son hallazgos diferentes, y colapsarlos en el momento de la importación ocultaría un problema de calidad de datos que merece la pena ver.

Los archivos se identifican por su contenido, no por su nombre

Renombra sales.csv a sales_FINAL_v2.csv, impórtalo de nuevo, y Tabulite reconoce el contenido y reutiliza la tabla existente en lugar de duplicarla. La identidad es el SHA-256 del archivo, calculado durante la pasada de importación en lugar de en una lectura aparte. Cambia un byte y se convierte en una nueva fuente con su propia tabla.

Todo lo importado vive en una sola base de datos (workspace/databases/main.sqlite), de modo que tu asistente puede hacer joins entre archivos con SQL normal. Cuando dos archivos reclamarían el mismo nombre de tabla — por ejemplo, un sales.csv en dos carpetas diferentes —, el segundo recibe un sufijo de su propio hash de contenido (sales y sales_4b11d3), lo que significa que un archivo concreto siempre acaba con el mismo nombre de tabla, independientemente del orden de importación.

Los resultados grandes van al disco, no al chat

query_sql() devuelve como máximo 1 000 filas y siempre lo indica ("truncated": true), lo que es un empujón para agregar en SQL en lugar de paginar un resultado grande hacia la conversación. Las consultas patológicas — un producto cartesiano accidental, un CTE recursivo sin límite — se cancelan después de un tiempo de espera.

Cuando el usuario realmente quiere las filas, export_query() ejecuta el mismo SQL de solo lectura sin límite de filas y transmite el cursor directamente a un archivo en workspace/exports/:

"Dame todas las transacciones por email de 2025 por más de $1.000 y expórtalas."

Tu asistente construye la consulta, llama a export_query() y te devuelve la ruta. Este es el resultado real con los datos de muestra incluidos:

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

Ni el servidor ni la conversación retienen nunca el resultado completo, así que esto funciona igual con 58 filas que con 5 millones.


Seguridad

Tus CSV nunca se modifican. source/ está montado como solo lectura a nivel de Docker. Todo lo que se escribe va a workspace/.

Toda consulta generada por IA es de solo lectura, garantizado en cuatro capas:

  1. la conexión se abre con file:…?mode=ro, de modo que el sistema operativo mantiene el archivo en modo solo lectura;

  2. PRAGMA query_only=ON hace que el propio SQLite rechace escrituras en ese manejador;

  3. la carga de extensiones está deshabilitada explícitamente;

  4. una callback set_authorizer() permite solo SQLITE_SELECT, SQLITE_READ, SQLITE_FUNCTION (salvo las funciones integradas que acceden al sistema de archivos) y SQLITE_RECURSIVE, denegando todo lo demás: escrituras, cambios de esquema, ATTACH/DETACH, todos los PRAGMA, control de transacciones, mantenimiento.

La capa 4 es el mecanismo real: se ejecuta dentro de SQLite durante la preparación de la sentencia, por lo que juzga lo que una consulta hace, no cómo está escrita. Un filtro de SQL se coloca delante como defensa en profundidad y para darle al modelo un error legible (only read-only statements are allowed; found 'DROP') en lugar de un simple not authorized.

Esta distinción funciona en ambos sentidos, y la suite de pruebas lo fija: CASE … END y la función escalar replace() son SQL analítico ordinario y siguen funcionando, mientras que REPLACE INTO, PRAGMA writable_schema = ON y load_extension() se rechazan.

Las rutas están contenidas. El servidor solo lee dentro de source/ y solo escribe dentro de workspace/exports/. El traversal de rutas (../), las rutas absolutas y los enlaces simbólicos que apuntan fuera del proyecto se rechazan; los nombres de los archivos de exportación se sanean y una exportación existente nunca se sobrescribe.

Sin autenticación — por diseño. El contenedor publica solo en 127.0.0.1 y está pensado para un cliente en la misma máquina. No lo expongas a una red.


Configuración

Todas son opcionales; configúralas en compose.yaml.

Variable

Valor por defecto

Qué controla

TABULITE_SOURCE_DIR

/project/source

directorio fuente de solo lectura

TABULITE_WORKSPACE_DIR

/project/workspace

espacio de trabajo escribible

TABULITE_NULL_MARKERS

,NULL,null,N/A,NA

valores importados como NULL de SQL

TABULITE_MAX_QUERY_ROWS

1000

límite de filas interactivo

TABULITE_QUERY_TIMEOUT

30

segundos antes de cancelar una consulta

TABULITE_EXPORT_TIMEOUT

600

segundos antes de cancelar una exportación

TABULITE_BATCH_SIZE

5000

filas por executemany() durante la importación

TABULITE_HOST / TABULITE_PORT

0.0.0.0 / 8000

dirección de enlace dentro del contenedor

TABULITE_ALLOWED_ORIGINS

orígenes de localhost

lista de orígenes permitidos (protección contra DNS rebinding)


Estructura del proyecto

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

Desarrollo

Ejecútalo sin Docker:

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

Ejecuta las pruebas:

pytest

211 pruebas cubren el descubrimiento de fuentes y el rechazo del traversal, la importación en streaming, el manejo de NULL frente a inválidos, la identidad SHA-256 (incluidos archivos renombrados y modificados), la asignación determinista de nombres de tabla, el perfilado y la inferencia de tipos, las funciones TRY_*, que AVG ignore los valores inválidos, consultas SELECT/GROUP BY/CTE/join/window, límites de resultados, cancelación de consultas, el cumplimiento de solo lectura tanto en la capa de filtrado como en la de autorización, la exportación CSV y JSON, el streaming de exportación, el saneamiento de nombres de archivo y la invocación de herramientas en una sesión MCP real dentro del proceso.

Stack: Python 3.11+, el sqlite3 de la biblioteca estándar y el SDK oficial de MCP para Python fijado en mcp==2.1.1 (API v2: MCPServer, host/port en run()). Sin pandas, sin NumPy, sin ORM — el núcleo es claramente Python ordinario: sqlite3.connect(), conn.executemany(), conn.create_function(), cursor.fetchmany().

Escala: un CSV de 133 MB / 2,000,000 de filas se importa y se perfila en unos dos minutos con la memoria del contenedor estable en torno a 100 MB; hacer agregaciones sobre él tarda un par de segundos. La importación está limitada por el disco, no por la RAM.


Solución de problemas

El puerto 8000 ya está en uso — cambia el lado del host del mapeo en compose.yaml ("127.0.0.1:8001:8000") y apunta tu cliente al nuevo puerto.

El cliente no puede conectarse — comprueba que el servidor está activo con curl http://localhost:8000/health y luego ejecuta docker compose logs -f.

Errores de permisos al escribir en workspace/ (Linux) — descomenta la línea user: en compose.yaml para que los archivos se creen con tu usuario y no con el usuario del contenedor.

Un archivo en source/ no aparece en el listado — solo se detectan .csv y .tsv, y se omiten los archivos ocultos.

"tabla desconocida" después de editar un CSV — modificar un archivo cambia su hash, así que vuelve a ejecutar import_source(); el contenido nuevo recibe su propia tabla.


Fuera de alcance

Sin LLM integrado, sin lenguaje natural a SQL en el servidor, sin ejecución arbitraria de Python, sin pandas/NumPy/matplotlib, sin Excel, DuckDB, Polars o Parquet, sin embeddings ni búsqueda vectorial, sin despliegue en la nube, autenticación, soporte multiusuario ni trabajos en segundo plano. Tu cliente de IA ya es la interfaz y la capa de razonamiento.

Licencia

MIT — haz lo que quieras con ello, mantén el aviso.

Las contribuciones son bienvenidas y se aceptan bajo la misma licencia (sin CLA, sin cesión de derechos de autor). Los derechos de autor permanecen en las personas que escribieron el código, lo cual es deliberado: se pretende que siga siendo un proyecto de código abierto en lugar de convertirse en el producto de alguien.

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