mcp-multi-db
This MCP server gives AI agents safe, read-only access to multiple SQL databases (PostgreSQL, MySQL, SQLite) through a single interface.
Discover configured databases with IDs, types, labels, policies, and effective row limits.
List tables/views in a configured database.
Inspect column metadata for a table.
Run read-only SQL (
SELECT,WITH,EXPLAIN) against a chosendatabase_id; writes are blocked.Rely on safety controls: SQL-text guard, database-level read-only transactions, row caps, and query timeouts.
Optionally use governance: per-database tool scopes, table allow/deny policies, column redaction, rate limits, and audit logs.
Ask in natural language via an MCP client, with schema discovery before querying.
Provides read-only SQL querying and schema discovery for MySQL databases.
Provides read-only SQL querying and schema discovery for PostgreSQL databases.
Provides read-only SQL querying and schema discovery for SQLite databases.
mcp-multi-db
One MCP server for all your SQL databases — read-only and safe by default.
Connect an AI agent to PostgreSQL, MySQL, and SQLite at the same time through a
single Model Context Protocol server. List
your databases in one config file and the agent picks which one to query by
database_id. Every query is enforced read-only at the database level, so it's
safe to point at real data.
Multi-engine, one server — Postgres, MySQL, and SQLite side by side; no separate server per database.
Read-only & safe by default — two-layer enforcement (SQL-text guard + database-level read-only transactions) plus row limits and query timeouts. See SECURITY.md.
Governed when you need it (opt-in) — per-database tool scope, a table allow/deny-list checked against the engine's own query plan, column redaction that survives aliasing, row/time/rate budgets, and a JSONL audit log. See Governance.
Schema-aware — the agent can discover databases, tables/views, and column metadata before it writes a query.
Zero query language to learn — just ask in natural language.
What an agent sees with a policy on — a denied table stays denied even
behind a CTE, a redacted column stays redacted even when renamed with AS,
and writes are refused. Real output; reproduce it with
node examples/demo/run-demo.mjs after npm run build.


The clip drives the real server over stdio (via the MCP SDK) against a seeded SQLite database. Regenerate it with
npm run build && vhs examples/demo.tape.
Tools
Tool | Description |
| List configured database connections |
| List tables/views in a database |
| Show column metadata for a table |
| Run read-only SQL ( |
Related MCP server: dbridge-mcp
Quick start
No install needed — npx fetches the package on first use.
1. Create databases.json
Copy the example and edit with your connection details:
cp databases.example.json databases.json{
"databases": [
{
"id": "local-sqlite",
"type": "sqlite",
"path": "/absolute/path/to/app.db",
"label": "Local dev SQLite"
},
{
"id": "analytics-pg",
"type": "postgres",
"connectionString": "postgresql://user:pass@localhost:5432/analytics",
"label": "Analytics Warehouse"
},
{
"id": "reporting-mysql",
"type": "mysql",
"connectionString": "mysql://user:pass@localhost:3306/reporting",
"label": "Reporting MySQL"
}
]
}Never commit
databases.json— it contains credentials. It's already gitignored if you cloned the repo. Prefer read-only database users (see Security).
2. Register the server with your MCP client
The server speaks MCP over stdio, so it works with any MCP-capable client.
Use npx mcp-multi-db as the command and pass MCP_DB_CONFIG (the path to
your databases.json). See mcp.example.json and
databases.example.json for templates.
Edit claude_desktop_config.json (macOS:
~/Library/Application Support/Claude/claude_desktop_config.json):
{
"mcpServers": {
"mcp-multi-db": {
"command": "npx",
"args": ["-y", "mcp-multi-db"],
"env": {
"MCP_DB_CONFIG": "/absolute/path/to/databases.json"
}
}
}
}Restart Claude Desktop afterward.
claude mcp add mcp-multi-db \
--env MCP_DB_CONFIG=/absolute/path/to/databases.json \
-- npx -y mcp-multi-dbAdd to ~/.cursor/mcp.json (or .cursor/mcp.json in your project), then open
Cursor Settings → Tools & MCP, restart the server, and use Agent mode:
{
"mcpServers": {
"mcp-multi-db": {
"command": "npx",
"args": ["-y", "mcp-multi-db"],
"env": {
"MCP_DB_CONFIG": "/absolute/path/to/databases.json"
}
}
}
}Any client that supports stdio MCP servers uses the same shape: command npx,
args ["-y", "mcp-multi-db"], and env MCP_DB_CONFIG pointing at your
databases.json. Consult your client's docs for where its MCP config lives.
Prefer a pinned global install?
npm install -g mcp-multi-db, then use commandmcp-multi-dbwith no args.
Configuration reference
Config is loaded from one of two environment variables, in order:
MCP_DB_CONFIG— path to a JSON file (recommended).MCP_DATABASES— inline JSON (useful when a file path is awkward).
The JSON may be either { "databases": [ ... ] } or a bare array. Each entry:
Field | Required | Notes |
| yes | Unique; the agent references this as |
| yes |
|
| postgres/mysql | Standard connection URI |
| sqlite | Absolute path to the |
| no | Human-friendly name shown to the agent |
| no | Extra context shown to the agent |
| no | Opt-in governance for this database — see Governance |
The object form also accepts top-level audit (see Governance) and limits:
Field | Default | Notes |
| 100 | Rows returned when a query passes no |
| 1000 | Ceiling on rows per query; at most 10,000, a hard cap no config can raise |
Each database can narrow these with policy.defaultRows / policy.maxRows
(never widen them), and list_databases reports every database's effective
rowLimits so the agent knows them before it asks. A LIMIT in the SQL must be
a plain integer, and adapters stop fetching at the cap whatever the SQL says.
Governance (opt-in)
Read-only answers can the agent change data? A policy answers the next
questions: what may it read, may it see personal data, and what did it run.
A database with no policy, and a config with no audit, behave exactly as
before.
{
"audit": { "path": "/var/log/mcp-multi-db/audit.jsonl", "sql": "full" },
"databases": [
{
"id": "crm",
"type": "postgres",
"connectionString": "postgresql://readonly@replica:5432/crm",
"policy": {
"tools": ["list_tables", "describe_table", "run_query"],
"tables": { "allow": ["public.accounts", "public.deals"] },
"columns": { "redact": ["public.accounts.email", "public.accounts.phone"] },
"maxRows": 200,
"timeoutMs": 10000,
"rateLimit": { "queriesPerMinute": 30 }
}
}
]
}Control | What it does | How it is enforced |
| Tools exposed for this database. Leave out | Checked before any database call. |
| Relations a query may read. Names match case-insensitively on trailing segments ( | From the engine's query plan, inside the read-only transaction, before the query runs — so aliases, CTEs, subqueries and views cannot hide a table. Postgres and SQLite only; see below. |
|
| From each result column's origin as the driver reports it, so |
| Row ceiling and default for this database. | Can only narrow the server-wide |
| Statement timeout (≤ 30000). | Postgres |
|
| Token bucket per database; the refusal says when to retry. |
| One JSON line per tool call: tool, database, SQL (or | Written before the response. A failed write warns on stderr; |
What else a policy changes. Under a table policy, list_tables and
describe_table only show tables the policy allows, and the system catalogs
(sqlite_master, pg_catalog, information_schema) are refused unless the
allow-list names them, because they describe every table. On a database with
redacted columns, an engine error raised while a query runs is replaced with
a generic message, because errors such as bad JSON path: '<value>' can echo
row values; parse and plan errors still come through.
What redaction does not stop. It hides values in results. It does not stop
a query from filtering, grouping or sorting by a redacted column, so
SELECT name FROM accounts WHERE email LIKE 'a%' returns no email but answers
a question about one, and enough such queries can reconstruct a value. If that
matters, do not expose the column at all (a view without it, or column grants)
and set a rateLimit.
Engine notes. MySQL's query plan reports table aliases, so a tables
policy on MySQL is rejected at startup — restrict tables with MySQL grants.
Redaction on MySQL matches the column name in any table, which can
over-redact but never under-redacts. Under a table or column policy, EXPLAIN
and (on Postgres) function scans are refused, because neither can be checked.
A function that reads another table inside its body does not appear in a plan;
the read-only database user is what bounds that. Full design and limits:
docs/governance.md.
Example prompts
"List my configured databases"
"What tables are in
local-sqlite?""Describe the
userstable inanalytics-pg""Run
SELECT COUNT(*) FROM ordersonreporting-mysql"
Security
Read-only only.
INSERT,UPDATE,DELETE, DDL, etc. are blocked — both by a SQL-text guard and by running each query inside a database-level read-only transaction (SQLite opens read-only). ASELECTthat calls a side-effecting function is still refused.Row limits. Results are capped (default 100, max 1000; configurable up to a hard cap of 10,000).
Query timeouts. Statements are bounded (30s) to avoid runaway queries.
Optional governance. Per-database tool scope, plan-checked table policy, alias-proof column redaction, budgets and an audit log — see Governance.
Use least privilege anyway. Prefer dedicated read-only DB users and read replicas. Full details and reporting in SECURITY.md.
Docker
A multi-stage Dockerfile is included. Build and run with your
databases.json mounted in:
docker build -t mcp-multi-db .
docker run --rm -i \
-v /absolute/path/to/databases.json:/config/databases.json:ro \
mcp-multi-dbThe image runs as a non-root user, ships only the built JS and runtime
node_modules (no toolchain), and speaks MCP over stdio just like the npx
install — so it slots into any MCP client by replacing the command and
args with the appropriate docker run -i invocation.
Development
npm install
npm run build # tsc -> build/, the artifact MCP clients run
npm test # builds, then runs the node:test suiteTests use Node's built-in test runner (no extra dependencies) and cover the read-only SQL guard and the SQLite adapter end to end. CI runs build + tests on every push and pull request.
Adding another database engine is a contained change: add the config variant in
src/config.ts, implement the SqlDatabasePort interface in a
new adapter under src/adapters/, and register it in the
adapter factory. New adapters must enforce read-only at the connection level —
not rely on the text guard alone. See docs/extending.md for
the full checklist.
Documentation
Architecture — components, request lifecycle, and the port-family design (with diagrams).
Extending — add a SQL engine or a non-SQL family.
Security — the two-layer read-only model and safe deployment.
The full index is in docs/.
Contributing
Issues and pull requests are welcome — see CONTRIBUTING.md for setup, the read-only invariants, and how to add a database engine.
License
ISC — see LICENSE.
Available Tools
4 toolsdescribe_tableC
Describe columns for a table in a configured database.
Inputs: table (required), database_id (required), schema.
list_databasesA
List all configured database connections (id, type, label, description, and policy when one is set: which tools are enabled, row cap, whether tables are restricted, which columns are redacted). Call this first to discover which database_id to use.
Inputs: no named parameters.
list_tablesC
List tables and views in a configured database.
Inputs: database_id (required), schema.
run_queryA
Execute a read-only SQL query (SELECT, WITH, EXPLAIN) on a configured database. Writes are blocked. If the database has a policy, queries reading tables outside it are refused with the reason, and redacted columns come back as "[redacted]" (listed in redacted).
Inputs: sql (required), database_id (required), limit.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 4 tools
The four tools split cleanly into query execution (run_query) and metadata introspection (list_databases, list_tables, describe_table). The only mild confusion risk is between the two 'list_' tools, but the descriptions make the connection-level vs table-level distinction explicit.
All names follow a consistent snake_case verb_noun pattern (run_query, list_tables, describe_table, list_databases). No mixed conventions or vague standalone verbs.
Four tools is a tight, well-scoped set for a read-only multi-database inspection server; each tool covers a distinct step of the discovery-to-query workflow with no filler.
The read-only lifecycle is well covered: discover connections, list tables, describe columns, then query. Minor gaps exist in schema introspection (no index, constraint, or relationship details, and no cross-database search), which agents would have to infer from run_query output.
Maintenance
Related MCP Connectors
Text-to-SQL MCP server: read-only queries on PostgreSQL, MySQL and SQL Server with your real schema
PostgreSQL, MySQL, and SQL Server in one session. 26 read-only MCP tools for AI agents.
Safe, read-only Postgres and MySQL access for AI agents. Audit log + column-level controls.
Analytical memory for AI agents: a real Postgres queried in plain English over MCP. One command.
Related MCP Servers
- FlicenseNot gradedqualityDmaintenanceA read-only MCP server that enables AI agents to explore database schemas and execute safe queries on PostgreSQL and MySQL.-
- AlicenseAqualityCmaintenanceRead-only MCP server that lets AI agents safely query SQLite, PostgreSQL, and MySQL/MariaDB. Enforces read-only transactions with column masking, row caps, query timeouts, EXPLAIN-based cost rejection, and rate limiting.734 npm2MIT
- FlicenseAqualityCmaintenanceA small MCP server that lets an LLM query PostgreSQL, MySQL, MariaDB, SQL Server, or SQLite databases safely — read-only, role-restricted, and with sensitive data blacked out.4-
- AlicenseNot gradedqualityDmaintenanceRead-only MCP server for SQL databases (SQLite/PostgreSQL) that enables listing tables, describing schemas, and executing SELECT queries with safety guardrails.MIT