duckdb-mcp
A fast, persistent DuckDB MCP server that enables AI assistants to query and manage local and cloud data. It supports:
SQL Querying: Execute
SELECTstatements and get results as text tables.SQL DDL/DML: Run write operations (
INSERT,UPDATE,DELETE,CREATE,DROP) without returning rows.Data Loading: Ingest CSV and Parquet files (local or
s3://) into tables.Introspection: List catalogs, databases, schemas, tables, columns, and loaded extensions.
Environment Inspection: List environment variables with masked values for security.
Version Check: Report the DuckDB version.
Initialization: Run custom SQL scripts on startup via
--init-sql.Security: Optional read-only mode, masking of secrets, and HTTP bearer token auth.
Extensibility: Enable unsigned community extensions.
Deployment: Available over stdio or HTTP.
Provides tools for querying DuckDB databases, loading CSV and Parquet files, and listing catalogs, schemas, tables, columns, extensions, environment variables, and DuckDB version.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@duckdb-mcpLoad sales.csv and show total revenue by region"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
๐ฆ DuckDB MCP Server
Give your AI assistant a blazing-fast, persistent SQL engine.
A minimal Model Context Protocol server that plugs DuckDB straight into Claude โ query CSVs, Parquet, and cloud data in plain language, at in-process speed.
โก ~10 ms / query ยท ๐ชถ 2 runtime deps ยท ๐งฐ 12 tools ยท ๐ read-only & secret-safe
โจ Why this one?
โก Fast | One persistent connection for the whole session โ no subprocess spawn, no reconnect. ~10 ms per query. |
๐ชถ Tiny | A focused |
๐งฐ Complete | 12 tools covering query, write, CSV/Parquet loading, and full catalog/schema/table introspection. |
๐ Safe by default | Engine-enforced |
โ๏ธ Cloud-ready | Query |
๐งฉ Extensible | Opt-in unsigned/community extensions (e.g. TA-Lib) via a single flag or env var. |
๐ Local or remote | Run over stdio (Claude Desktop) or streamable HTTP โ one |
๐ Token auth | Protect the HTTP transport with a bearer token from a single env var. |
โ Trustworthy | Fully typed, linted, and tested โ CI runs black + ruff + mypy + pytest on Python 3.12 & 3.13. |
Related MCP server: mcp-server-motherduck
๐ Quick start
1. Run it โ no install needed (via uv):
uvx --from git+https://github.com/wuqunfei/duckdb-mcp-mini duckdb-mcp --db :memory:2. Point Claude Desktop at it โ add this to claude_desktop_config.json and restart Claude:
{
"mcpServers": {
"duckdb": {
"command": "uvx",
"args": ["--from", "git+https://github.com/wuqunfei/duckdb-mcp-mini", "duckdb-mcp", "--db", ":memory:"]
}
}
}3. Ask away ๐ฌ
"Load
~/data/sales.csvand show me total revenue by region."
That's it. Tools are available immediately. ๐
๐ฆ Install
pip install . # from a clone, into the current environment
pip install -e ".[dev]" # for development (editable + dev tools)Or run straight from Git without installing:
uvx --from git+https://github.com/wuqunfei/duckdb-mcp-mini duckdb-mcp --db :memory:โน๏ธ This package is not published to PyPI โ install from source or run from Git as shown above.
Requirements: ๐ Python 3.11โ3.14 ยท ๐ฆ DuckDB 1.5.2 (pinned in pyproject.toml, easy to change) ยท ๐ MCP SDK 2.0.0+
๐ Run
The installed console script is duckdb-mcp. It speaks MCP over stdio, so you'll normally launch it from an MCP client โ but you can start it directly too:
duckdb-mcp # in-memory, read-write
duckdb-mcp --db /path/to/analytics.duckdb --schema main
duckdb-mcp --db analytics.duckdb --init-sql init.sql
duckdb-mcp --db analytics.duckdb --read-only
python -m duckdb_mcp.cli --db :memory: # module form
uv run duckdb-mcp --db :memory: # via uv, no installCLI arguments
Flag | Description |
| Database path ( |
| Default schema. Default: |
| Path to a SQL file executed once on startup |
| Open the database read-only (default: read-write) |
| Allow unsigned/community extensions (default: off; env: |
|
|
| Bind host for the |
| Bind port for the |
| Log level to stderr: |
๐ Debugging: run with
--log-level DEBUG(orLOG_LEVEL=DEBUG) to log every incoming tool request โ name and arguments โ to stderr, so you can see exactly what a client sends:DEBUG duckdb_mcp: tool request: query args={'sql': 'SELECT 42 AS answer'}Logs always go to stderr (never stdout, which is the protocol channel in stdio mode).
๐ Read-only mode
--read-only opens the connection via DuckDB's own read-only flag, so writes are blocked at the engine level (not by inspecting SQL):
โ
SELECTworks normally๐ซ
INSERT/UPDATE/DELETE/CREATE/DROPare rejected by DuckDB๐ก Perfect for shared analytics/reporting databases where accidental writes must be impossible
๐ Transports
The server speaks two MCP transports โ pick with --transport:
Transport | Flag | Use it for |
stdio (default) |
| Local clients that launch the process, e.g. Claude Desktop |
streamable HTTP |
| Remote / networked clients โ the current MCP HTTP transport (single |
# stdio (default) โ the process talks over stdin/stdout
duckdb-mcp --db analytics.duckdb๐ Run an HTTP server
Configuration splits cleanly in two: args set the database and network binding, environment variables carry secrets and toggles (so they stay out of ps and shell history).
# 1) Environment โ secrets & toggles
export MCP_AUTH_TOKEN="a-long-random-secret" # require Bearer auth (recommended)
export ALLOW_UNSIGNED_EXTENSIONS=true # optional: allow community extensions
export AWS_ACCESS_KEY_ID="${AWS_ACCESS_KEY_ID}" # optional: for s3:// queries
export AWS_SECRET_ACCESS_KEY="${AWS_SECRET_ACCESS_KEY}"
# 2) Args โ database + network binding
duckdb-mcp \
--transport http \
--host 0.0.0.0 \
--port 8000 \
--db /data/analytics.duckdb \
--init-sql /data/init.sql \
--read-only
# โ serving at http://0.0.0.0:8000/mcp--init-sql runs a SQL file once, when the connection opens. Combined with --read-only it must be read-only-safe (loading extensions or setting options is fine โ creating tables is not):
-- /data/init.sql
INSTALL httpfs; LOAD httpfs; -- read remote/S3 data
SET memory_limit = '4GB';Drop
--read-onlyif your init script needs to create tables or views.
No install? Run the same thing straight from Git:
MCP_AUTH_TOKEN="a-long-random-secret" \
uvx --from git+https://github.com/wuqunfei/duckdb-mcp-mini duckdb-mcp \
--transport http --host 0.0.0.0 --port 8000 --db :memory:Setting | Kind | Notes |
| arg | Required to serve over HTTP |
| arg | Bind address (default |
| arg | Same as stdio mode |
| env | Require |
| env | Community extensions (or the |
| env | Cloud credentials for |
| both |
|
Verify it's up (with MCP_AUTH_TOKEN set, a missing/wrong token returns 401):
curl -s -o /dev/null -w '%{http_code}\n' \
-X POST http://127.0.0.1:8000/mcp \
-H 'Authorization: Bearer a-long-random-secret' \
-H 'Accept: application/json, text/event-stream' \
-H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"ping"}'
# 401 = missing/invalid token ยท 400 = reached the server (a real client does the full MCP handshake)๐ Bearer token
Setting MCP_AUTH_TOKEN turns on auth for the HTTP transport:
๐ฑ Environment only โ read from
MCP_AUTH_TOKEN, never a CLI flag, so it stays out ofpsand shell history.๐ HTTP only โ ignored for
stdio(the client owns the process); setting it there prints a warning.โ ๏ธ Access control, not identity โ every caller with the token gets full database access. Pair it with
--read-onlyand network controls; it is not per-user auth.
๐
--hostdefaults to127.0.0.1(localhost only). Bind to0.0.0.0only on a trusted network, and always withMCP_AUTH_TOKENset.
๐ Connect a client
MCP client config (Claude Desktop or any client that supports an HTTP URL) โ omit headers if you didn't set a token:
{
"mcpServers": {
"duckdb": {
"url": "http://127.0.0.1:8000/mcp",
"headers": { "Authorization": "Bearer a-long-random-secret" }
}
}
}From Python (the mcp client libraries ship httpx2):
import asyncio
import httpx2
from mcp import ClientSession
from mcp.client.streamable_http import streamable_http_client
URL = "http://127.0.0.1:8000/mcp"
HEADERS = {"Authorization": "Bearer a-long-random-secret"} # omit if no MCP_AUTH_TOKEN
async def main():
async with httpx2.AsyncClient(headers=HEADERS) as http:
async with streamable_http_client(URL, http_client=http) as streams:
read, write = streams[0], streams[1]
async with ClientSession(read, write) as session:
await session.initialize()
tools = await session.list_tools()
print("tools:", [t.name for t in tools.tools])
result = await session.call_tool("query", {"sql": "SELECT 42 AS answer"})
print(result.content[0].text)
asyncio.run(main())๐ฅ๏ธ Configure Claude Desktop
Add one of the following to your claude_desktop_config.json, then restart Claude.
Run from Git (no install):
{
"mcpServers": {
"duckdb": {
"command": "uvx",
"args": ["--from", "git+https://github.com/wuqunfei/duckdb-mcp-mini", "duckdb-mcp", "--db", ":memory:"]
}
}
}Run from a local clone:
{
"mcpServers": {
"duckdb": {
"command": "uv",
"args": ["run", "--directory", "/absolute/path/to/duckdb-mcp-mini", "duckdb-mcp", "--db", "/home/user/analytics.duckdb", "--schema", "main"]
}
}
}After pip install . (command on PATH):
{
"mcpServers": {
"duckdb": {
"command": "duckdb-mcp",
"args": ["--db", "/home/user/analytics.duckdb", "--read-only"]
}
}
}๐ Config file locations
macOS:
~/Library/Application Support/Claude/claude_desktop_config.jsonLinux:
~/.config/Claude/claude_desktop_config.jsonWindows:
%APPDATA%\Claude\claude_desktop_config.json
๐ Multiple servers
Add more entries under mcpServers with distinct names (e.g. duckdb-dev, duckdb-prod), each with its own --db/--schema.
โ๏ธ Environment variables & cloud data
Any variables set in the client's env block are available to DuckDB during connection (handy for S3/cloud credentials). Values support ${VAR_NAME} interpolation, so you can reference the system environment instead of hardcoding secrets into the config file:
{
"mcpServers": {
"duckdb": {
"command": "uvx",
"args": ["--from", "git+https://github.com/wuqunfei/duckdb-mcp-mini", "duckdb-mcp", "--db", ":memory:"],
"env": {
"AWS_ACCESS_KEY_ID": "${AWS_ACCESS_KEY_ID}",
"AWS_SECRET_ACCESS_KEY": "${AWS_SECRET_ACCESS_KEY}",
"AWS_REGION": "${AWS_REGION}",
"S3_BUCKET": "my-data-bucket"
}
}
}
}Set the referenced variables in your shell first:
export AWS_ACCESS_KEY_ID="your-key"
export AWS_SECRET_ACCESS_KEY="your-secret"
export AWS_REGION="us-east-1"Then query cloud data directly:
SELECT * FROM read_parquet('s3://my-data-bucket/data.parquet')๐ Interpolation happens before the DuckDB connection is opened, so credentials are ready in time for connection setup and any
--init-sql. Use thelist_environmentstool to confirm what's set โ values are always masked.
๐งฉ Community & unsigned extensions
DuckDB only loads signed extensions by default. To use community or self-hosted extensions, enable unsigned extensions with the --allow-unsigned-extensions flag or the ALLOW_UNSIGNED_EXTENSIONS environment variable (default: off):
duckdb-mcp --db :memory: --allow-unsigned-extensions
# or
ALLOW_UNSIGNED_EXTENSIONS=true duckdb-mcp --db :memory:In a Claude Desktop config:
{
"mcpServers": {
"duckdb": {
"command": "uvx",
"args": ["--from", "git+https://github.com/wuqunfei/duckdb-mcp-mini", "duckdb-mcp", "--db", ":memory:", "--allow-unsigned-extensions"]
}
}
}The flag is applied as a connection-time setting, so it's already on โ just INSTALL and LOAD through the execute tool. For example, neuesql/atm_talib (TA-Lib for DuckDB):
INSTALL talib FROM 'https://neuesql.github.io/atm_talib';
LOAD talib;โ ๏ธ Unsigned extensions run native code with full trust. Only enable this for extensions from sources you trust.
๐งฐ Tools (12)
Category | Tool | Purpose |
๐ Core |
| Run a |
โ๏ธ Core |
| Run a write statement ( |
๐ฅ File I/O |
| Load a CSV file into a table ( |
๐ฅ File I/O |
| Load a Parquet file into a table ( |
๐ Introspection |
| List catalogs |
๐ Introspection |
| List databases |
๐ Introspection |
| List schemas |
๐ Introspection |
| List tables in the current schema |
๐ Introspection |
| Describe a table's columns |
๐ Introspection |
| List loaded extensions |
๐ Introspection |
| List environment variables as |
๐ท๏ธ Introspection |
| Report the DuckDB version |
query vs execute โ query is for reads (fetches rows, formats a table); execute is for writes/DDL (runs the statement, returns "Executed successfully").
๐๏ธ Architecture
launch (cli.main)
โ
DuckDBSession(...) # src/duckdb_mcp/session.py
โโ interpolate ${VAR} across os.environ โ runs BEFORE connecting
โโ duckdb.connect(db, read_only=...) โ one persistent connection
โโ run --init-sql (if provided) โ falls back to :memory: on error
โ
create_server(session) # src/duckdb_mcp/server.py
โโ registers 12 tools on an mcp MCPServer; each calls dispatch_tool()
โ
_serve(session, transport=...) # src/duckdb_mcp/cli.py
โโ run_stdio_async() | run_streamable_http_async()๐งฉ
session.pyholdsDuckDBSessionwith no MCP imports, so the connection/formatting logic is unit-testable with onlyduckdb.๐๏ธ
server.pyholds the_HANDLERSregistry (single source of truth for the tool set) plusdispatch_tool()and the thin typed MCP registrations.๐๏ธ
cli.pyparses args, builds the session, and serves.
Good to know:
๐ If connecting to
--db(or running--init-sql) fails, the session degrades to an in-memory read-write connection rather than crashing.โ ๏ธ SQL is f-string interpolated (table names, filepaths). This is intentional for a local single-user tool โ inputs are not sanitized.
๐ ๏ธ Development
pip install -e ".[dev]" # or: uv sync --extra dev
black --check src tests # format (drop --check to apply)
ruff check src tests # lint
mypy src # type check
pytest # tests
pytest tests/test_server.py::test_dispatch_query # run a single test๐ Project layout
duckdb-mcp-mini/
โโโ src/duckdb_mcp/
โ โโโ __init__.py # package version + DuckDBSession export
โ โโโ session.py # persistent DuckDB session (no MCP deps)
โ โโโ server.py # tool registry + dispatch + MCP server wiring
โ โโโ cli.py # argument parsing + entrypoint
โโโ tests/ # pytest suite
โโโ .github/workflows/
โ โโโ ci.yml # lint + type + test on py3.11โ3.14
โ โโโ release.yml # build on tag (PyPI publish disabled)
โโโ pyproject.toml
โโโ LICENSE
โโโ README.md๐ CI / CD
CI (
.github/workflows/ci.yml) runs black, ruff, mypy, and pytest on Python 3.11, 3.12, 3.13, and 3.14 for every push/PR tomain, and checks thatduckdb-mcp --helpworks.CD (
.github/workflows/release.yml) builds the sdist/wheel on av*tag and attaches them to the GitHub Release. PyPI publishing is intentionally disabled โ thepublish-pypijob is gated behindif: false; see the comments in that file to enable it later.
๐ค Contributing
Issues and PRs are welcome! Please keep the footprint small (the minimalism is a feature ๐ชถ), and make sure black / ruff / mypy / pytest all pass before opening a PR.
Built with ๐ฆ + ๐ ยท Licensed under MIT
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
- Alicense-qualityDmaintenanceA Model Context Protocol server implementation that connects AI assistants to DuckDB, enabling them to query and analyze data from various sources including CSV, Parquet, JSON, and cloud storage through SQL.17MIT
- Alicense-qualityDmaintenanceAn MCP server that enables SQL analytics on DuckDB and MotherDuck databases, allowing AI assistants and IDEs to query and analyze data.MIT
- AlicenseAqualityDmaintenanceA local MCP server implementation that interacts with DuckDB and MotherDuck databases, providing SQL analytics capabilities to AI Assistants and IDEs.1MIT
- Alicense-qualityDmaintenanceA local MCP server enabling AI assistants to query and analyze data via DuckDB SQL engine, supporting local files, memory, S3, and MotherDuck.29Apache 2.0
Related MCP Connectors
Analytical memory for AI agents: a real Postgres queried in plain English over MCP. One command.
GibsonAI MCP server: manage your databases with natural language
A comprehensive Model Context Protocol (MCP) server that enables AI assistants to interact with yoโฆ
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/wuqunfei/duckdb-mcp-mini'
If you have feedback or need assistance with the MCP directory API, please join our Discord server