Skip to main content
Glama
wuqunfei

duckdb-mcp

by wuqunfei

๐Ÿฆ† 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.

CI Python DuckDB MCP Code style License

โšก ~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 src/ package, just two runtime dependencies (duckdb, mcp). No bloat, no magic.

๐Ÿงฐ Complete

12 tools covering query, write, CSV/Parquet loading, and full catalog/schema/table introspection.

๐Ÿ”’ Safe by default

Engine-enforced --read-only mode, plus a list_environments tool that masks every secret (****).

โ˜๏ธ Cloud-ready

Query s3:// Parquet directly, with ${VAR} interpolation to keep credentials out of config files.

๐Ÿงฉ 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 --transport flag.

๐Ÿ”‘ 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.csv and 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 install

CLI arguments

Flag

Description

--db, --database

Database path (:memory: or /path/to/db.duckdb). Default: :memory:

--schema

Default schema. Default: main

--init-sql

Path to a SQL file executed once on startup

--read-only

Open the database read-only (default: read-write)

--allow-unsigned-extensions

Allow unsigned/community extensions (default: off; env: ALLOW_UNSIGNED_EXTENSIONS)

--transport

stdio (default) or http (streamable HTTP)

--host

Bind host for the http transport (default: 127.0.0.1)

--port

Bind port for the http transport (default: 8000)

--log-level

Log level to stderr: DEBUG/INFO/WARNING/ERROR/CRITICAL (default: WARNING; env: LOG_LEVEL). DEBUG logs every tool request

๐Ÿ› Debugging: run with --log-level DEBUG (or LOG_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):

  • โœ… SELECT works normally

  • ๐Ÿšซ INSERT / UPDATE / DELETE / CREATE / DROP are 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)

--transport stdio

Local clients that launch the process, e.g. Claude Desktop

streamable HTTP

--transport http

Remote / networked clients โ€” the current MCP HTTP transport (single /mcp endpoint)

# 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-only if 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

--transport http

arg

Required to serve over HTTP

--host / --port

arg

Bind address (default 127.0.0.1:8000)

--db / --schema / --init-sql / --read-only

arg

Same as stdio mode

MCP_AUTH_TOKEN

env

Require Authorization: Bearer <token> (env-only, no flag)

ALLOW_UNSIGNED_EXTENSIONS

env

Community extensions (or the --allow-unsigned-extensions flag)

AWS_*, any ${VAR}

env

Cloud credentials for s3:// reads (see the Environment variables & cloud data section)

--log-level / LOG_LEVEL

both

DEBUG logs every tool request to stderr

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 of ps and 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-only and network controls; it is not per-user auth.

๐Ÿ”’ --host defaults to 127.0.0.1 (localhost only). Bind to 0.0.0.0 only on a trusted network, and always with MCP_AUTH_TOKEN set.

๐Ÿ”— 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.json

  • Linux: ~/.config/Claude/claude_desktop_config.json

  • Windows: %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 the list_environments tool 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

query

Run a SELECT and return the result as a text table

โœ๏ธ Core

execute

Run a write statement (INSERT/UPDATE/DELETE/CREATE/DROP); returns a status message

๐Ÿ“ฅ File I/O

read_csv

Load a CSV file into a table (table_name defaults to data)

๐Ÿ“ฅ File I/O

read_parquet

Load a Parquet file into a table (table_name defaults to data)

๐Ÿ“š Introspection

list_catalogs

List catalogs

๐Ÿ“š Introspection

list_databases

List databases

๐Ÿ“š Introspection

list_schemas

List schemas

๐Ÿ“š Introspection

list_tables

List tables in the current schema

๐Ÿ“š Introspection

list_columns

Describe a table's columns

๐Ÿ“š Introspection

list_extensions

List loaded extensions

๐Ÿ” Introspection

list_environments

List environment variables as key: value, with values masked (****, or empty when unset)

๐Ÿท๏ธ Introspection

check_version

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.py holds DuckDBSession with no MCP imports, so the connection/formatting logic is unit-testable with only duckdb.

  • ๐Ÿ—‚๏ธ server.py holds the _HANDLERS registry (single source of truth for the tool set) plus dispatch_tool() and the thin typed MCP registrations.

  • ๐ŸŽ›๏ธ cli.py parses 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 to main, and checks that duckdb-mcp --help works.

  • CD (.github/workflows/release.yml) builds the sdist/wheel on a v* tag and attaches them to the GitHub Release. PyPI publishing is intentionally disabled โ€” the publish-pypi job is gated behind if: 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

Install Server
A
license - permissive license
A
quality
A
maintenance

Maintenance

โ€“Maintainers
โ€“Response time
0dRelease cycle
3Releases (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

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/wuqunfei/duckdb-mcp-mini'

If you have feedback or need assistance with the MCP directory API, please join our Discord server