tabulite-mcp
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 --buildEl 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/mcpCualquier 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 |
| Archivos CSV en |
| columnas, delimitador y algunas filas de muestra — sin importar |
| transmitir un CSV a SQLite y perfilarlo |
| tablas importadas con recuentos de filas y su origen |
| perfil compacto de cada columna |
| detalle completo de una columna, con ejemplos |
| algunas filas, para ver cómo son los datos |
| SQL analítico de solo lectura (máximo 1 000 filas) |
| 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_REALprofile_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 wrongUn 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') -- NULLTambié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 |
|
|
(vacío) |
|
|
|
|
|
|
|
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:
la conexión se abre con
file:…?mode=ro, de modo que el sistema operativo mantiene el archivo en modo solo lectura;PRAGMA query_only=ONhace que el propio SQLite rechace escrituras en ese manejador;la carga de extensiones está deshabilitada explícitamente;
una callback
set_authorizer()permite soloSQLITE_SELECT,SQLITE_READ,SQLITE_FUNCTION(salvo las funciones integradas que acceden al sistema de archivos) ySQLITE_RECURSIVE, denegando todo lo demás: escrituras, cambios de esquema,ATTACH/DETACH, todos losPRAGMA, 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 |
|
| directorio fuente de solo lectura |
|
| espacio de trabajo escribible |
|
| valores importados como NULL de SQL |
|
| límite de filas interactivo |
|
| segundos antes de cancelar una consulta |
|
| segundos antes de cancelar una exportación |
|
| filas por |
|
| dirección de enlace dentro del contenedor |
| 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.yamlDesarrollo
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-mcpEjecuta las pruebas:
pytest211 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.
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