A lightweight Model Context Protocol (MCP) server written in Rust that connects MySQL and PostgreSQL databases to LLMs inside editors like VS Code and Zed.
- Overview
- Features
- Prerequisites
- Installation
- Configuration
- Available Tools
- Persistent Connections
- Usage Examples
- Project Structure
- Building from Source
- Environment Variables
- Connection String Format
- License
db-mcp implements the Model Context Protocol over stdio (newline-delimited JSON-RPC 2.0). When registered as an MCP server in your editor, it exposes a set of database tools that your AI assistant can call to:
- Connect / disconnect from one or more databases simultaneously
- Save connection credentials persistently and reconnect by name
- Inspect schemas and table definitions
- Execute arbitrary SQL queries and get results back as structured JSON
- Multi-database support — MySQL, MariaDB, and PostgreSQL via
sqlx - Multiple simultaneous connections — open several databases at once and switch between them by name
- Persistent connections — save credentials with
configure_server, reconnect instantly with just a name - MCP-compliant — works with any editor or agent that speaks the Model Context Protocol
- Schema introspection — list databases, list tables, describe individual tables, or dump the entire schema at once
- Safe credential redaction — passwords are masked in all log output and connection-info responses
- Async & lightweight — built on Tokio; single binary with no runtime dependencies
- Static musl binary available — copy to any Linux machine or Docker container and run without installing anything
| Tool | Version |
|---|---|
| Rust | 1.75 or later |
| Cargo | ships with Rust |
| A running MySQL / MariaDB or PostgreSQL instance | any recent version |
git clone https://github.com/your-org/db-mcp.git
cd db-mcp
cargo build --release
# Binary is at ./target/release/db-mcp# Add the musl target once
rustup target add x86_64-unknown-linux-musl
# Install the musl linker (Debian / Ubuntu)
sudo apt-get install -y musl-tools
# Build
cargo build --release --target x86_64-unknown-linux-musl
# Binary is at ./target/x86_64-unknown-linux-musl/release/db-mcp
# Verify it is fully static
ldd ./target/x86_64-unknown-linux-musl/release/db-mcp
# → statically linkedThe musl binary has zero runtime dependencies — no glibc, no OpenSSL, no system libraries. Copy it to any x86-64 Linux machine or Docker container and run it directly.
cargo install --path .Register db-mcp as an MCP server in your editor so that it is launched automatically when the editor starts.
Add the following to your settings.json (or to the workspace .vscode/settings.json):
{
"mcp.servers": {
"db-mcp": {
"command": "/absolute/path/to/db-mcp",
"args": [],
"transport": "stdio"
}
}
}Add the following to your ~/.config/zed/settings.json:
{
"context_servers": {
"db-mcp": {
"command": {
"path": "/absolute/path/to/db-mcp",
"args": []
}
}
}
}Qwen CLI reads MCP server configuration from ~/.qwen/settings.json. Add the following block:
{
"mcpServers": {
"db-mcp": {
"command": "/absolute/path/to/db-mcp",
"args": [],
"transport": "stdio"
}
}
}If the file does not exist yet, create it:
mkdir -p ~/.qwen
touch ~/.qwen/settings.jsonThen paste the JSON above into the file. On the next qwen invocation the db-mcp tools will be available automatically.
Tip: You can verify the server is loaded by running
qwen mcp list—db-mcpshould appear in the output.
Gemini CLI reads MCP server configuration from ~/.gemini/settings.json. Add the following block:
{
"mcpServers": {
"db-mcp": {
"command": "/absolute/path/to/db-mcp",
"args": [],
"transport": "stdio"
}
}
}If the file does not exist yet, create it:
mkdir -p ~/.gemini
touch ~/.gemini/settings.jsonThen paste the JSON above into the file. Gemini CLI will launch db-mcp automatically as a subprocess and communicate with it over stdio.
Tip: You can verify the server is loaded by running
gemini mcp list—db-mcpshould appear in the output.
Once registered, the following tools are exposed to the LLM:
| Tool | Required args | Description |
|---|---|---|
connect_database |
connection_string or saved_config_name |
Open a connection. Optionally label it with connection_name. |
disconnect_database |
— | Close a connection by connection_name, or the first active one. |
get_database_info |
— | Return type, host, and status for a connection. |
list_connections |
— | List all currently open connection names. |
list_databases |
— | List all databases / schemas visible to the connected user. |
list_tables |
— | List all tables in the connected database. |
describe_table |
table_name |
Return column definitions (name, type, nullability, default, key). |
get_full_schema |
— | Dump the complete schema — every table and all its columns. |
execute_query |
sql |
Run any SQL. SELECT/SHOW/EXPLAIN → JSON rows; INSERT/UPDATE/DELETE → rows-affected count. |
configure_server |
name, ip, port, username, password, dbtype |
Save connection details to ~/.db-mcp/config.json for future use. |
All tools that operate on a connection accept an optional
connection_nameargument. If omitted, the first open connection is used automatically.
db-mcp can save your database credentials to a config file so you never have to type them again. Use configure_server once, then reconnect in any future session with just a name.
Call configure_server with the connection details:
configure_server(
name = "pgdb",
ip = "127.0.0.1",
port = "5432",
username = "user",
password = "secret",
dbtype = "postgres",
database = "pgdb" ← optional; defaults to username if omitted
)
The credentials are written to ~/.db-mcp/config.json immediately. The entry survives restarts — db-mcp loads the file automatically every time it starts.
In any later session, connect with just the saved name — no URL or credentials needed:
connect_database(saved_config_name = "pgdb")
# Give it a custom label for this session (optional)
connect_database(saved_config_name = "pgdb", connection_name = "production")
The file is newline-delimited JSON (one compact JSON object per line). Each line represents one saved connection:
{"name":"pgdb","ip":"127.0.0.1","port":5432,"username":"root","password":"secret","dbtype":"postgres","database":"sasdb"}
{"name":"local-mysql","ip":"127.0.0.1","port":3306,"username":"dev","password":"devpass","dbtype":"mysql","database":"appdb"}| Field | Type | Description |
|---|---|---|
name |
string | Unique identifier used to reconnect |
ip |
string | Hostname or IP address of the database server |
port |
number | Port number (5432 for PostgreSQL, 3306 for MySQL) |
username |
string | Database username |
password |
string | Database password |
dbtype |
string | "postgres" or "mysql" |
database |
string | Target database / schema. For PostgreSQL, defaults to username if blank |
PostgreSQL default database: If
databaseis left empty, PostgreSQL connects to a database with the same name as the username (standard libpq behaviour). Always specifydatabaseexplicitly to avoid surprises.
| Platform | Path |
|---|---|
| Linux / macOS | ~/.db-mcp/config.json |
Fallback (no $HOME) |
./db_config.json (current working directory) |
The parent directory ~/.db-mcp/ is created automatically on first write.
You can add, edit, or remove entries directly in the file with any text editor. Rules to follow:
- One JSON object per line — do not use pretty-printed / multi-line JSON.
- All seven fields should be present;
databasemay be an empty string"". - Duplicate
namevalues: the last one in the file wins on load. - Deleting a line removes that saved connection permanently.
Example — add a new entry manually:
echo '{"name":"staging","ip":"192.168.1.50","port":5432,"username":"app","password":"apppass","dbtype":"postgres","database":"stagingdb"}' \
>> ~/.db-mcp/config.jsonconnect_database(connection_string = "postgres://alice:secret@localhost:5432/mydb")
connect_database(
connection_string = "mysql://root:pass@127.0.0.1:3306/shop",
connection_name = "local-shop"
)
configure_server(
name = "pgdevdb",
ip = "localhost",
port = "5432",
username = "postgres",
password = "postgres",
dbtype = "postgres",
database = "myapp"
)
connect_database(saved_config_name = "pgdevdb")
connect_database(saved_config_name = "pgdevdb", connection_name = "dev")
connect_database(saved_config_name = "pgdb", connection_name = "prod")
list_connections()
# → { "connections": ["dev", "prod"] }
list_tables(connection_name = "dev")
execute_query(connection_name = "prod", sql = "SELECT COUNT(*) FROM orders")
list_tables()
describe_table(table_name = "orders")
execute_query(sql = "SELECT id, name, created_at FROM users WHERE active = true LIMIT 20")
disconnect_database(connection_name = "prod")
db-mcp/
├── Cargo.toml # Package manifest & dependencies
├── Cargo.lock
└── src/
├── main.rs # Entry point — JSON-RPC 2.0 stdio loop
├── db.rs # Multi-connection state, db operations, schema introspection
├── tools.rs # MCP tool definitions (list) and dispatch logic
├── protocol.rs # JSON-RPC & MCP protocol types and helpers
└── config.rs # Persistent connection config — load/save ~/.db-mcp/config.json
main.rs— reads newline-delimited JSON-RPC from stdin, dispatches to handlers, writes responses to stdout. Handlesinitialize,ping,tools/list, andtools/callMCP methods.db.rs— ownsConfigVsDBstate(aHashMap<name → SharedState>) andDbState(pool + db kind + URL per connection). Implements all async database operations viasqlx::AnyPoolso the same code path works for both MySQL and PostgreSQL.tools.rs— declares the MCP tool manifest returned to the client, implementsresolve_state_for_nameto look up a connection by optional name, and routes everytools/callrequest to the correctdb.rsfunction.protocol.rs— lightweight JSON-RPC 2.0 request / response structs and MCP helper builders (tool_ok,tool_err,make_tool,str_prop).config.rs— loads~/.db-mcp/config.jsonon startup, exposesconfigure_server(persist) andadd_temporary_entry(session-only), and providesget_connection_urlto reconstruct a connection URL from a saved entry.
# Debug build (faster compile, slower binary)
cargo build
# Release build (optimised)
cargo build --release
# Fully static musl build (runs on any x86-64 Linux, no dependencies)
cargo build --release --target x86_64-unknown-linux-musl
# Run directly
cargo run
# Run with verbose logging
RUST_LOG=db_mcp=debug cargo runrustup target add x86_64-unknown-linux-musl # add Rust target
sudo apt-get install -y musl-tools # install musl-gcc linker| Build | Path |
|---|---|
| Debug | target/debug/db-mcp |
| Release (glibc) | target/release/db-mcp |
| Release (musl, static) | target/x86_64-unknown-linux-musl/release/db-mcp |
| Variable | Default | Description |
|---|---|---|
RUST_LOG |
db_mcp=info |
Log level filter. Logs are written to stderr so they never pollute the MCP stdio channel. Example: RUST_LOG=db_mcp=debug |
# PostgreSQL
postgres://username:password@host:5432/database
# MySQL / MariaDB
mysql://username:password@host:3306/database
PostgreSQL default database: Omitting the database segment (
postgres://user:pass@host:5432/) causes PostgreSQL to connect to a database with the same name as the username. Always include the database name to be explicit.
Security note: Connection strings contain credentials. Avoid committing them to version control. Use
configure_serverto store them in~/.db-mcp/config.jsoninstead, or pass them at runtime through your editor's MCP server configuration.
| Crate | Purpose |
|---|---|
tokio |
Async runtime |
serde / serde_json |
JSON serialisation |
sqlx |
Async database driver (MySQL + PostgreSQL) |
anyhow |
Ergonomic error handling |
tracing / tracing-subscriber |
Structured logging to stderr |