Skip to main content
Glama
davidmrguo

tabulite-mcp

by davidmrguo

Tabulite MCP

스프레드시트에는 너무 크고 채팅에 붙여넣기에는 너무 큰 CSV 파일을 분석하세요. 데이터 자체 대신 로컬 SQLite 런타임을 AI 어시스턴트에 제공하는 방식으로.

Tabulite MCP(줄여서 Tabulite)는 로컬 MCP 서버입니다. CSV 파일이 있는 폴더를 지정하면 데스크톱 AI 클라이언트가 파일을 SQLite로 가져오고, 열이 실제로 무엇을 담고 있는지 살펴보고, SQL을 작성하여 질문에 답할 수 있습니다. 데이터의 단 한 행도 여러분의 머신을 떠나거나 대화에 들어오지 않습니다.

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

서버 내부에는 LLM이 없습니다. 사고는 AI 클라이언트가 합니다. Tabulite는 클라이언트가 참고할 메타데이터, 탐색용 읽기 전용 SQL 인터페이스, 그리고 답이 문장이 아니라 데이터셋일 때 디스크로 가는 직접 경로를 제공합니다.


왜 필요한가

500 MB CSV에 대해 AI에게 물어보면 선택지가 좋지 않습니다. 샘플을 붙여넣으면 답을 놓치고, 전체를 업로드하면 컨텍스트 창을 태우며(그리고 데이터를 어딘가로 보내고), 아니면 직접 스크립트를 작성해야 합니다.

단일 머신과 단일 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)가 포함되어 있어 바로 시도해볼 수 있습니다. AI 클라이언트를 연결한 다음(아래 참조) 이렇게 물어보세요:

"sales.csv를 분석하세요. 어떤 채널이 가장 많은 매출을 올렸나요?"

어시스턴트는 list_sources(), import_source("sales.csv"), profile_table("sales")를 호출한 다음 다음과 같은 SQL을 작성할 것입니다:

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;

AI 클라이언트 연결

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/에 저장됩니다.


AI가 사용할 수 있는 도구

도구

기능

list_sources()

source/ 아래의 CSV 파일과 크기 및 가져오기 상태

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이 되고, 앞자리가 0인 제품 코드는 정수가 되며, "2025-13-40"은 조용히 NULL이 됩니다. 따라서 저장소는 파일이 말한 내용을 그대로 유지하고, 해석은 나중에 보이고 되돌릴 수 있는 단계에서 이루어집니다.

프로필은 열이 의미하는 바를 AI에게 알려줍니다

가져온 후 모든 열이 프로파일링되고 결과는 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%가 해당 타입으로 파싱될 때만 타입이 할당됩니다. 프로필은 AI를 위한 증거이며 저장 계층에 대한 지시가 결코 아닙니다. 가져온 데이터가 추측에 맞게 다시 쓰여지는 일은 없습니다.

CAST 대신 TRY_* 함수

SQLite의 CAST는 위험할 정도로 관대합니다:

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

수천 개의 'unknown' 값이 있는 열에 AVG()를 적용하면 0으로 취급되어 조용히 평균에 포함됩니다. 그래서 Tabulite는 모든 연결에서 엄격한 변환 함수를 등록합니다:

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

추가로 사용 가능한 것: TRY_INTEGER, TRY_DATE, TRY_DATETIME, TRY_BOOLEAN. SQLite의 집계 함수는 NULL을 건너뛰므로 잘못된 값은 0으로 계산되지 않고 제외됩니다. 어시스턴트는 분모를 확인할 수도 있습니다:

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 값

저장 결과

125.40

"125.40"

(비어 있음)

NULL

N/A

NULL

unknown

"unknown"

-

"-"

기본 마커: 빈 문자열, NULL, null, N/A, NA. "이 필드는 비어 있었다"와 "이 필드에는 쓰레기가 들어 있었다"는 서로 다른 발견 사항이며, 가져오기 시점에 이를 합쳐버리면 볼 가치가 있는 데이터 품질 문제가 숨겨집니다.

파일은 이름이 아닌 내용으로 식별됩니다

sales.csvsales_FINAL_v2.csv로 이름을 바꾸고 다시 가져오면 Tabulite는 내용을 인식하고 중복 테이블을 만드는 대신 기존 테이블을 재사용합니다. 식별 기준은 파일의 SHA-256이며, 별도의 읽기가 아니라 가져오기 과정 중에 계산됩니다. 한 바이트만 바뀌어도 별도의 테이블을 가진 새 소스가 됩니다.

가져온 모든 것은 하나의 데이터베이스(workspace/databases/main.sqlite)에 저장되므로 어시스턴트는 일반 SQL로 파일 간 조인을 할 수 있습니다. 두 파일이 같은 테이블 이름을 요구할 때 — 예를 들어 서로 다른 두 폴더에 sales.csv가 있는 경우 — 두 번째 파일은 자신의 콘텐츠 해시에서 접미사를 얻습니다(salessales_4b11d3). 따라서 특정 파일은 가져오기 순서와 관계없이 항상 같은 테이블 이름을 사용합니다.

큰 결과는 채팅이 아닌 디스크로

query_sql()은 최대 1,000행을 반환하며 항상 그 사실을 알립니다("truncated": true). 이는 큰 결과를 대화에 페이지 단위로 나누어 가져오기보다 SQL에서 집계하라는 신호입니다. 병리적 쿼리 — 예를 들어 우발적인 카티전 곱, 무제한 재귀 CTE — 는 타임아웃 후 취소됩니다.

사용자가 실제로 행을 원할 때 export_query()행 상한 없이 동일한 읽기 전용 SQL을 실행하고 커서를 workspace/exports/ 아래의 파일로 직접 스트리밍합니다:

"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행이든 500만 행이든 동일하게 작동합니다.


안전

CSV 파일은 절대 수정되지 않습니다. source/는 Docker 수준에서 읽기 전용으로 마운트됩니다. 쓰여지는 모든 것은 workspace/로 갑니다.

AI가 생성한 모든 쿼리는 읽기 전용이며, 네 계층으로 강제됩니다:

  1. 연결이 file:…?mode=ro로 열리므로 OS가 파일을 읽기 전용으로 유지합니다;

  2. PRAGMA query_only=ON으로 SQLite 자체가 해당 핸들에서 쓰기를 거부합니다;

  3. 확장 프로그램 로딩이 명시적으로 비활성화됩니다;

  4. set_authorizer() 콜백이 SQLITE_SELECT, SQLITE_READ, SQLITE_FUNCTION(파일 시스템에 닿는 내장 함수 제외) 및 SQLITE_RECURSIVE만 허용하고 그 외 모든 것 — 쓰기, 스키마 변경, ATTACH/DETACH, 모든 PRAGMA, 트랜잭션 제어, 유지보수 — 을 거부합니다.

4번째 계층이 실제 메커니즘입니다. 문 준비 중 SQLite 내부에서 실행되므로 쿼리 텍스트가 어떻게 쓰여졌는지가 아니라 쿼리가 무엇을 하는지를 판단합니다. 그 앞에는 심층 방어를 위해 SQL 스크러버가 있으며, 모델에게 단순한 not authorized 대신 읽을 수 있는 오류(only read-only statements are allowed; found 'DROP')를 제공합니다.

이 구분은 양방향으로 작동하며 테스트 스위트가 이를 고정합니다. CASE … ENDreplace() 스칼라 함수는 일반적인 분석 SQL이므로 계속 작동하는 반면, REPLACE INTO, PRAGMA writable_schema = ONload_extension()은 거부됩니다.

경로는 격리됩니다. 서버는 source/ 안에서만 읽고 workspace/exports/ 안에서만 씁니다. 탐색(../), 절대 경로, 프로젝트 외부를 가리키는 심볼릭 링크는 거부됩니다. 내보내기 파일 이름은 정화되며 기존 내보내기 파일을 덮어쓰는 일은 없습니다.

인증 없음 — 의도된 설계입니다. 컨테이너는 127.0.0.1에만 공개되며 같은 머신의 클라이언트를 위한 것입니다. 네트워크에 노출하지 마세요.


구성

모두 선택 사항이며 compose.yaml에서 설정합니다.

변수

기본값

제어 내용

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 출처

Origin 허용 목록(DNS 리바인딩 보호)


프로젝트 구조

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/조인/윈도우 쿼리, 결과 제한, 쿼리 취소, 스크러버와 authorizer 계층 모두에서의 읽기 전용 강제, CSV 및 JSON 내보내기, 내보내기 스트리밍, 파일 이름 정화, 실제 프로세스 내 MCP 세션을 통한 도구 호출.

기술 스택: Python 3.11+, 표준 라이브러리의 sqlite3, 그리고 mcp==2.1.1로 고정된 공식 MCP Python SDK(v2 API: MCPServer, run()의 host/port). pandas도, NumPy도, ORM도 없습니다. 핵심은 평범한 Python입니다: sqlite3.connect(), conn.executemany(), conn.create_function(), cursor.fetchmany().

규모: 133MB / 2,000,000행 CSV는 컨테이너 메모리가 약 100MB로 일정하게 유지된 채 약 2분 만에 가져오기와 프로파일링이 완료됩니다. 이에 대한 집계에는 몇 초가 걸립니다. 가져오기는 RAM이 아닌 디스크에 의해 제한됩니다.


문제 해결

포트 8000 이미 사용 중compose.yaml에서 매핑의 호스트 쪽("127.0.0.1:8001:8000")을 변경하고 클라이언트를 새 포트로 지정하세요.

클라이언트가 연결할 수 없음curl http://localhost:8000/health로 서버가 실행 중인지 확인한 다음 docker compose logs -f를 실행하세요.

workspace/에 쓰기 권한 오류(Linux)compose.yaml에서 user: 줄의 주석을 해제하여 파일이 컨테이너 사용자 대신 사용자 자신의 권한으로 생성되게 하세요.

source/의 파일이 나열되지 않음.csv.tsv만 발견되며, 점으로 시작하는 파일은 건너뜁니다.

CSV 편집 후 "알 수 없는 테이블" — 파일을 변경하면 해시가 변경되므로 import_source()를 다시 실행하세요. 새 콘텐츠는 자체 테이블을 갖게 됩니다.


범위 외

임베디드 LLM도, 서버에서의 자연어-to-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