tableski
Every spreadsheet is a table — tableski serves them to your agents in SQL.
Excel, CSV, Parquet, JSON — one penguin-family binary that turns your data files into queryable tables and serves them to AI agents over the Model Context Protocol.
- Single binary, stateless Streamable HTTP — built on the emperor-mcp framework (Emperor Profile P1 conformant: framed output, forwarded credentials, no session affinity).
- SQL engine powered by Apache DataFusion.
- Hatched in the kowalski rookery — the
-skiis the family name.
Install
Prebuilt binaries (macOS arm64/x86_64, Linux x86_64, Windows x86_64) are attached to GitHub releases:
|
Windows: download the .zip from the releases page. Or via cargo:
Connect an MCP client
tableski is a standard MCP server over stateless Streamable HTTP — point any client at the server URL:
# Claude Code
// Claude Desktop (claude_desktop_config.json) via mcp-remote
Or raw HTTP: POST JSON-RPC to / with Accept: application/json (see below).
Run
# One model for every format: --file registers by extension, table name = file stem
# (.csv .parquet .json/.ndjson/.jsonl; workbooks .xlsx/.xls/.ods = one table per sheet)
# (--csv/--xlsx flags still work; name collisions get _2/_3 suffixes)
Ask it something (tools: list_tables, query_sql, get_schema, column_statistics,
export_result — all output framed as data per Emperor Profile E8). SQL joins work across
formats: an xlsx sheet against a Parquet file against an NDJSON log. export_result writes
a query's rows to .csv/.xlsx — only inside the --export-dir sandbox (relative names,
no ..; the tool is disabled unless the operator passes the flag):
# => ada 150.5, linus 99.99 — a SQL join across two sheets of one workbook
Sheet names are slugified into table names (People → people, 2024 Orders →
t_2024_orders); first rows become column names when they look like headers
(--headers auto|first-row|none to override). Column types are inferred: integers,
floats, booleans, dates (as timestamps), text.
Real-world workbooks are messy — tableski handles the mess: both Excel date systems
(1900 and 1904), formula cells (cached values ingest; #DIV/0!-style errors become
NULL), merged cells (top-left value, NULL continuations), display formats (raw values,
always), ragged rows, format-only padding (trimmed), unicode sheet names, and a --max-rows
cap that fails loudly instead of cutting silently. The nasty-workbook corpus lives in
fixtures/corpus/ — every trait has a fixture and a test.
Try the whole flow in one go: ./scripts/demo.sh (spreadsheet → question → SQL answer →
exported workbook). demo.tape renders it as a GIF with vhs.
Status: CSV, Excel (xlsx/xls/ods, hardened against real-world workbooks), Parquet, and NDJSON all serve today; results export to csv/xlsx; binaries ship per release. Launch is next.
How it compares
Honest positioning — different tools solve different problems:
| tableski | openpyxl-based Excel MCP servers | Google-Sheets MCPs | |
|---|---|---|---|
| Ask questions over data | SQL engine (DataFusion): joins, aggregates, filters — across sheets and file formats | cell/range reads; analysis logic lands in the model's context | per-API calls against live Sheets |
| Formats | xlsx/xls/ods + CSV + Parquet + NDJSON, one table model | xlsx (often read and write, incl. formatting/charts — tableski doesn't edit workbooks) | Google Sheets only |
| Deployment | single static binary, stateless Streamable HTTP (Emperor Profile P1) | Python runtime + deps | hosted API + OAuth |
| Output safety | every tool result framed as data (prompt-injection mitigation) | typically raw | typically raw |
If you need to edit workbooks (styles, charts, cell writes), pair tableski with a writer-oriented server; tableski is the analysis and export engine.
License
MIT