← csvql blog

csvql vs DuckDB MCP Servers: Which Read-Only CSV Server Should an Agent Get?

2026-11-08

If you want Claude Desktop or Claude Code to answer questions about CSV files with SQL, there are now several MCP servers to pick from. Most of them wrap DuckDB. csvql (published as csvql-query on npm and PyPI) is its own engine. This page compares them on the things that matter when the caller is an agent: what it can read, what it can do to your machine, how much comes back per call, and how much SQL it understands.

Everything here is from the projects’ own READMEs as of November 2026, plus csvql’s measured numbers. Where a README does not say, the table says “not stated”, and that is a real difference: the agent gets whatever the default is.

The candidates

What the agent can do

The agent asks the tool to… csvql duckdb-mcp duckcoop DuckDB CLI
Write a file (COPY ... TO) No. The parser has no write statement No. Statement gate rejects COPY, INSERT, CREATE, ATTACH, EXPORT Not stated. CREATE TEMP TABLE is allowed Yes
Read a file outside its folder (../, /etc/hosts) No. PathOutsideAllowedRoot; the folder is --root or the start directory No. DuckDB allowed_directories, set from --dir No. Sandbox root is the CLI argument Yes
Fetch a URL, install an extension No. The binary has no network code No. enable_external_access=false, auto-install off, lock_configuration=true Not stated Yes, by default
Return a whole large table in one call No. About 100 rows / 12 KB per call Capped at 1,000 rows by default, cells cut at 200 characters Not stated Yes
Read a column you marked private No, with --deny-columns email,ssn Only if you pre-build views Not stated Only if you pre-build views
Run for minutes on a bad query Scans are linear; no timeout flag 30 second timeout by default; README notes it cannot cap CPU or memory Not stated Yes
Keep an audit trail of queries --audit file.jsonl Not stated Not stated No

The read-only story differs in kind, not just degree. csvql’s parser has no INSERT, UPDATE, DELETE, DROP or COPY to gate; there is nothing to bypass. duckdb-mcp gates statements in front of a DuckDB that can write, and its README says so plainly. duckcoop allows CREATE TEMP TABLE, which is scratch space rather than a write to your files, but the README does not state what else is blocked.

What SQL the agent gets

  csvql DuckDB-based servers
Joins Inner and left hash joins, chained All join types
Window functions, UNION, CTEs No, they error Yes
Subqueries col IN (SELECT ...) only Full
Parquet, JSON input No Yes
Excel .xlsx First sheet No (duckdb-mcp says export to CSV first)
Scalar functions in WHERE DATEDIFF, DATEADD, EXTRACT only Full
Correctness check 128 queries verified against DuckDB on every commit It is DuckDB

If the questions are mostly “group this by that”, “top N”, “join these two exports” and “how many rows match”, csvql covers it. If the agent needs window functions, Parquet or the long tail of DuckDB’s SQL, use one of the DuckDB servers. csvql errors on what it does not support rather than guessing, so the agent finds out on the first query, not after a wrong answer.

Memory on the box next to the data

The reason csvql exists at all. Same GROUP BY department on raw CSV, peak memory, one process per engine:

File csvql DuckDB polars
0.72 GB 54 MB 221 MB 1,137 MB
8 GB 74 MB 367 MB 1,754 MB

Speed is in the same band as DuckDB on raw CSV. The DuckDB-based MCP servers inherit DuckDB’s memory profile, plus a Python or Node runtime. This matters when the server runs on a small VM next to the files, which is the setup the --root sandbox is designed for.

Setup, side by side

# csvql: one binary, registers itself in Claude Code and Claude Desktop
brew install melihbirim/csvql/csvql
cd ~/data && csvql install

# duckdb-mcp: needs uv
claude mcp add duckdb -- uvx duckdb-mcp --dir ~/data

# duckcoop: build from source, Node
npm run build && node dist/cli.js ~/data

Claude Desktop starts MCP servers in /, not in your project folder. csvql refuses to start there without --root, and csvql install writes the --root for you. duckdb-mcp defaults to the current directory when --dir is unset; check what that resolves to under Desktop.

When to pick which

Check the csvql column yourself: every claim is in the README with the command that demonstrates it. The DuckDB CLI defaults: duckdb -c "SELECT current_setting('enable_external_access')".