DEV Community

Вячеслав Зубов
Вячеслав Зубов

Posted on • Originally published at zeslava.github.io

db-mcp: connect your database to AI

I needed a private utility for work and personal projects: read data from databases, but never write. Existing solutions weren't what I wanted — those that existed were JS/Python packages. I just needed a simple binary: download and run, without Node.js or other overhead. Plus, always nice to build something yourself :)

So I built db-mcp (github) — a lightweight Rust binary that:

  • Reads from PostgreSQL, MySQL/MariaDB, SQLite, and ClickHouse via URL scheme
  • Works with Claude, OpenCode, Jan, Zed, and any MCP-compatible client
  • Simple and safe by design: read-only (SELECT only), parameterized queries
  • Builds once with all backends enabled — choose your database at startup
  • Ships as a fully static Linux binary (musl, no glibc at runtime)

Why this was needed

At work, I often need to give Claude access to data: fetch information, analyze, provide context for scripting. Requirements were:

  • Safe (read-only, parameterized queries)
  • Private (runs locally, nothing sent to cloud)
  • Universal (not just Claude — I use OpenCode, Zed, Jan for different tasks)

How it works

db-mcp is a stdio MCP server written in Rust. You point it at a database URL via the --database-url flag or the DATABASE_URL env var:

db-mcp --database-url postgres://user:pass@localhost:5432/mydb
db-mcp --database-url mysql://user:pass@localhost:3306/mydb
db-mcp --database-url sqlite:///absolute/path/to/data.db
db-mcp --database-url clickhouse://default:pass@localhost:8123/default
Enter fullscreen mode Exit fullscreen mode

The server detects the engine from the URL scheme and connects. Then Claude/OpenCode/Jan can use three tools:

Tool Params What it does
list_tables List user tables
describe_table table, schema? Columns, types, nullability
query sql Run a SELECT, returns JSON rows

A query call returns plain JSON, so the model gets real values to reason about:

[
  { "id": 1, "email": "ada@example.com", "created_at": "2026-05-01 09:12:33" },
  { "id": 2, "email": "linus@example.com", "created_at": "2026-05-03 14:50:01" }
]
Enter fullscreen mode Exit fullscreen mode

Safe by design

SELECT-only enforcement lives in the server, not in the adapters. Anything that doesn't start with SELECT (case-insensitive, after trim) is rejected — that even blocks CTE tricks like WITH ... INSERT ... RETURNING. Every adapter uses parameterized queries, never string-concatenated SQL. For an extra layer, point it at a read-only database role.

Setup for different tools

All clients launch the same binary; the difference is just where the config file lives. I prefer passing the URL through DATABASE_URL so it isn't visible in the args list.

Claude Code (CLI)

cli

claude mcp add db \
  --env DATABASE_URL=postgres://user:pass@localhost:5432/mydb \
  -- /absolute/path/to/db-mcp
Enter fullscreen mode Exit fullscreen mode

claude.json

{
  "mcpServers": {
    "db-mcp": {
      "type": "stdio",
      "command": "/absolute/path/to/db-mcp",
      "args": [],
      "env": {
        "DATABASE_URL": "postgres://user:pass@localhost:5432/mydb"
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Restart Claude and the tools appear.

OpenCode

In your opencode.json (project or ~/.config/opencode/opencode.json):

{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "database": {
      "type": "local",
      "command": ["db-mcp", "--database-url", "sqlite:///home/me/work/data.sqlite"],
      "enabled": true
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Jan

Jan has built-in MCP support (Settings → MCP Servers). The equivalent config is a command + args + env entry:

{
  "database": {
    "command": "/absolute/path/to/db-mcp",
    "args": [],
    "env": {
      "DATABASE_URL": "postgres://prod.example.com:5432/analytics"
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Zed

In settings.json (custom MCP servers live under context_servers, not lsp):

{
  "context_servers": {
    "db-mcp": {
      "enabled": true,
      "remote": false,
      "command": "/absolute/path/to/db-mcp",
      "args": [],
      "env": {
        "DATABASE_URL": "postgres://user:pass@localhost:5432/mydb"
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

In practice

Scenario 1: development context

In Claude Code:
"Help me write a migration to add a new field.
Check the current users table structure."

Claude:
1. Calls db-mcp (describe_table) → gets current schema
2. Sees existing fields and constraints
3. Generates a correct migration for your actual database
Enter fullscreen mode Exit fullscreen mode

Scenario 2: data analysis in OpenCode

In OpenCode analyzing metrics:
"How many active users this month?
What are the most popular features?"

OpenCode uses db-mcp (query), the model sees real numbers
and provides analysis grounded in actual data.
Enter fullscreen mode Exit fullscreen mode

Scenario 3: debugging in Jan

Jan with db-mcp helps debug:
"Are there orders without user_id in the orders table?"

Jan queries through db-mcp, sees there are 12 such records,
suggests how to fix it.
Enter fullscreen mode Exit fullscreen mode

Installation

Quick install (Linux and macOS Apple Silicon) — auto-detects OS/arch, verifies the checksum, installs to ~/.local/bin by default:

curl --proto '=https' --tlsv1.2 -sSf \
  https://raw.githubusercontent.com/zeslava/db-mcp/main/install.sh | sh
Enter fullscreen mode Exit fullscreen mode

Or grab a release tarball manually (Linux x86_64/aarch64, macOS arm64, Windows x86_64):

TARGET=x86_64-unknown-linux-gnu
curl -sSL "https://github.com/zeslava/db-mcp/releases/latest/download/db-mcp-${TARGET}.tar.gz" \
  | tar -xz
install -m 755 "db-mcp-${TARGET}/db-mcp" "$HOME/.local/bin/db-mcp"
Enter fullscreen mode Exit fullscreen mode

Or build from source:

git clone https://github.com/zeslava/db-mcp
cd db-mcp
cargo build --release
./target/release/db-mcp --database-url postgres://localhost/mydb
Enter fullscreen mode Exit fullscreen mode

Benefits of this approach

  • ✅ One binary for four engines (PostgreSQL, MySQL/MariaDB, SQLite, ClickHouse)
  • ✅ Works everywhere: Claude, OpenCode, Jan, Zed, any MCP client
  • ✅ No runtime overhead (no JS, Python) — pure Rust, static musl binary on Linux
  • ✅ Safe by design: SELECT-only, parameterized queries
  • ✅ Private: runs locally or in your infrastructure
  • ✅ Simple setup: just a database URL

What's next

  • Support for more databases (and beyond)
  • Query logging and audit trails
  • Per-table access control (for more granular permissions)
  • Server mode

If db-mcp sounds useful, star it on GitHub — issues and PRs welcome.

Top comments (1)

Collapse
 
alexshev profile image
Alex Shev

Read-only is the right default for database MCP. The moment an agent can write, the safety problem changes from “can it answer?” to “can it mutate production state correctly under ambiguous instructions?”

For database tools, I would want query limits, table allowlists, explainable generated SQL, and clear separation between exploration and action. Natural-language access is useful, but the blast radius needs to stay boring.