MCP server29 tools · 8 databases · read-only by design

Read everything.Change nothing.

Universal DB MCP gives Claude Code, Cursor and any MCP agent governed, read-only access to PostgreSQL, MySQL, MariaDB, ClickHouse, Oracle, SQL Server, Db2 and SQLite. Writes through it aren't discouraged. They're refused.

stdio or streamable HTTPNo telemetry, everSigned releasesOpen source, Apache-2.0
agent session · connected to universal-db
you ›Pull two rows from every database we have and line them up.
universal-db · db_federated_query (4 connections, one call)
connectionidlabelmock_pg1MB-ALPHAmock_pg2MB-BRAVOmock_mysql1R. Haddadmock_mysql2T. Nakamuramock_oracle1ET910mock_oracle2SQ317mock_clickhouse5incomingmock_clickhouse724outgoing
PostgreSQL · MySQL · Oracle · ClickHouse, merged: 8 rows in 0.05 s
you ›Great. Now clean out the test customers.
universal-db · db_query
WITH gone AS (DELETE FROM sales.customers RETURNING *) SELECT count(*) FROM gone
POLICY_VIOLATION: statement contains a disallowed construct (Delete)
Refused before it reached the database. Nothing was changed.

Tool calls and results are real output from the project's test databases. The prompts are illustrative.

Plugs into Claude CodeClaude DesktopCursorVS CodeClineany MCP client
Reads from PostgreSQLMySQLMariaDBClickHouseOracleSQL ServerDb2SQLite
The problem

Hand an agent a connection string and you've handed it DROP TABLE.

01

Prompts aren't permissions.

“Only run SELECT” is a request to a model, not a control. The first confident mistake lands on production data.

02

A regex isn't a parser.

A filter looking for DELETE at the start of a statement never sees the one inside a CTE, behind a MySQL executable comment, or after a semicolon.

03

Even reads can hurt.

A long scan under the wrong isolation level takes share locks, and your production writes queue up behind an agent's curiosity.

Universal DB MCP closes all three, with layers that don't trust each other.

How it works

Three layers between the agent and your data.

The guard refuses every write on every engine. Where the database has a read-only switch, the session refuses them too. Whatever does run is bounded, masked and audited.

01 · SQL GUARD

Reads SQL the way your database does.

Every statement becomes a syntax tree, and the guard walks all of it before anything runs.

  • One statement per call, no stacking
  • No DML, DDL, SELECT INTO, sequence access or locking hints, even inside a CTE
  • Functions from a closed allowlist; tables schema-qualified and inside your allowlist
  • Can't parse it? Refused, never passed through
02 · SESSION PROFILE

Pins the session before the first query.

Applied right after connecting and read back from the server, so you can see it took effect.

  • Server-side read-only on PostgreSQL, MySQL, MariaDB, ClickHouse and SQLite, by default
  • Db2 at UR, SQL Server at READ UNCOMMITTED: no share locks on your tables
  • Lock-wait and statement timeouts; sessions named for your DBAs
  • Server refuses read-only or the isolation level? The connection fails closed
  • String, quoting and dialect settings held where the guard parsed them, so the server reads the statement the guard checked
03 · LIMITS, MASKING, AUDIT

Bounds and records everything that runs.

Big tables stay big on the server. What comes back is capped, scrubbed and logged.

  • Row, byte and cell ceilings, applied while fetching, not after
  • Passwords, tokens, national IDs and card numbers masked by where each value comes from, through aliases, CTEs and UNIONs
  • Credentials from env vars or 0600 files, never in results or logs
  • Every call audited with a SQL fingerprint. No audit, no query
Try to break it

Throw your worst SQL at the guard.

Twelve ways an agent, or a prompt injection, might try to write, lock or escape. Every verdict is the guard's real output, and a test in the repository fails if this page and the guard ever disagree.

PostgreSQLA normal read
SELECT id, name, region
FROM sales.customers
WHERE region = 'north'
LIMIT 50
ALLOWEDOne read statement, every table schema-qualified inside the allowlist. It runs with row, byte and time limits.

Guard policy for this demo: connection warehouse, schema allowlist sales.

Beyond SELECT

Understand a whole data estate without writing SQL.

29 tools turn an agent into a careful analyst. It maps, searches, profiles, documents and cross-checks your databases, and every one of those calls is read-only and bounded.

db_search_values

Search every database at once

Find where a value lives across every permitted table of every connection. Case-insensitive, time-budgeted, sensitive columns skipped, and no SQL from the agent.

4 engines · 10 tables · 2 hits · 0.21 s
mock_mysqltestdb.cuppingstaster =R. Haddad(cupping 1)
mock_mysqltestdb.cuppingstaster =R. Haddad(cupping 3)
db_federated_query · db_federated_join

Ask every database the same question

One call runs on many connections and merges results by column shape. A join reconciles two results from different engines inside the server, masked per connection.

connectionidlabel mock_pg1MB-ALPHA mock_mysql1R. Haddad mock_oracle1ET910 mock_clickhouse5incoming
db_review_schema

An optimization review in one call

Profiles each table on a bounded sample and ranks findings with their evidence. It recommends; it never changes anything.

100 tables reviewed in 1.34 s
nullable_never_null456
statistics_missing99
integer_range_fits_smaller_type1
low_cardinality1
db_document_schema

Documentation that writes itself

A Markdown data dictionary from catalog metadata: types, keys, indexes, comments and relationships. Row values never appear.

## main.accounts *table, ~20 rows (catalog_estimate)* | account_id | INTEGER | integer/numeric | no | | PK | | | customer_id | INTEGER | integer/numeric | no | | FK -> main.customers(customer_id) | |
db_get_catalog

One type vocabulary for eight engines

Every table, column, key and index in one call, with declared types mapped to portable ones an ETL layer can build on.

oracleNUMBER(12,2)→decimal
mssqlnvarchar(max)→string
clickhouseLowCardinality(String)→text
mssqluniqueidentifier→uuid
oracleCLOB→text · lob
db_explain

Query plans on every engine, never executed

EXPLAIN on PostgreSQL, MySQL, ClickHouse and SQLite. EXPLAIN PLAN on Oracle and Db2. SHOWPLAN on SQL Server. The statement is planned, not run, and EXPLAIN ANALYZE is refused whatever the configuration says. On ClickHouse, which evaluates subqueries while it plans, planning gets a 1,000-row read ceiling, or a warning where the account's profile refuses one. A MySQL TREE or JSON plan that could show a masked value is withheld, because MySQL prints the values it reads while planning.

PostgreSQLMySQLMariaDBClickHouseOracleSQL ServerDb2SQLite
-- 2,000,000-row table, answered in 0.01 s
Bitmap Heap Scan on tall (cost=4.74..159.69 rows=40 width=44)
  Recheck Cond: (customer_id = 42)
  ->  Bitmap Index Scan on ix_tall_customer
See all 29 tools

Connect

  • db_list_connections
  • db_test_connection
  • db_get_capabilities

Browse

  • db_list_catalogs
  • db_list_databases
  • db_list_schemas
  • db_list_tables
  • db_get_table
  • db_list_columns
  • db_list_views
  • db_list_synonyms
  • db_list_routines
  • db_list_indexes
  • db_search_metadata
  • db_get_relationships
  • db_get_statistics

Read

  • db_validate_query
  • db_query
  • db_sample_table
  • db_explain
  • db_get_query_history

Discover

  • db_get_catalog
  • db_profile_table
  • db_search_values
  • db_infer_relationships
  • db_review_schema
  • db_document_schema

Federate

  • db_federated_query
  • db_federated_join
Compatibility

8 databases. The versions you actually run.

Each connector runs the same 22-check probe against real server images: catalog, keys, indexes, bounded queries, profiling, value search, query plans and the session read-back. 26 of 26 images pass.

PostgreSQL

22/22
121314151617

Read-only transactions at the server, lock and statement timeouts.

MySQL

22/22
5.78.08.4

Read-only transactions, lock and execution-time limits.

MariaDB

22/22
10.611.4

Same connector as MySQL, statement-time limits.

ClickHouse

19/22
23.824.324.825.3

readonly profile pinned. No foreign keys, routines or composite unique indexes to check.

Oracle

22/22
11g R218c21c23

Thin and thick mode. Thick mode reaches accounts that only have legacy password verifiers.

SQL Server

22/22
201720192022

READ UNCOMMITTED and a lock timeout. Plans through SHOWPLAN.

Db2 LUW

22/22
11.5.811.5.9

Isolation UR enforced, a lock timeout, and WITH UR accepted in statements.

SQLite

unit-tested
any file

Opened read-only with query_only, for local files and demos.

Oracle 12c has no redistributable test image, so it hasn't been run. SQL Server needs Microsoft ODBC Driver 18 on the machine running the server, and Oracle thick mode needs Instant Client. On ClickHouse, only the account's profile (max_memory_usage) bounds the server's own memory, so set one.

Air-gap ready

Built for networks the internet never touches.

The most valuable data often lives on isolated networks. Universal DB MCP installs there from a USB stick and never phones home.

No telemetry Never calls an LLM No runtime downloads CycloneDX SBOM HTTP with bearer tokens
  1. Build

    A staging machine with internet access builds one offline bundle per platform: every wheel pinned by hash, OS packages and an SBOM.

  2. Sign

    The bundle is signed with Ed25519, and every file in it is listed with its SHA-256. So is every file on the release stick, installer scripts included.

  3. Carry it in

    USB stick or any trusted channel. Before anything on the stick runs, the site checks its signed file list with the release key it already trusts, using the host's own openssl or the bootstrap an earlier release installed. On Ubuntu the bootstrap then copies the .deb packages to a root-only folder and checks them again, and dpkg installs those copies, never the stick's. It also refuses a stick older than the release it last installed.

  4. Verify, then run

    The signature and every file hash are checked before anything executes. Any mismatch stops the install.

  5. Install

    A .deb for Ubuntu 24.04 or a .pkg for macOS sets up the service; pip runs with --no-index --require-hashes.

  6. Upgrade and roll back

    Each upgrade builds beside the running install and keeps the previous release. One command rolls back, and from this release on the installers and the container image loader refuse to replace a newer release unless you ask for it.

Receipts

Receipts, not promises.

Every number here comes from a test run or an evidence file in the repository.

26/26server images pass the 22-check connector probe
12,548automated tests passing
2.8sto catalog a 2,002-table schema, end to end
0.08sto profile a 2-million-row table on a 50,000-row sample
401returned for a wrong HTTP token, before any tool runs

What we haven't verified yet

A tool that guards your data should be straight about its own gaps. These are open, and the ledger tracks every one.

Read the full ledger
  • Windows installer. The source and its test script ship; it needs a Windows host to build and run.
  • Apple Developer ID signing. The macOS package is unsigned for now.
  • Oracle 12c. There is no redistributable image to test against.
  • Service start under a real systemd. The package gate runs in a container.
  • Observing attempted egress. Offline runs prove nothing gets out, not that nothing tries.
  • A CI run on GitHub Actions. The workflow has been replayed locally; it hasn't run on GitHub yet.
  • This build on the version matrix and the package gates. The recorded runs predate the fixes from the latest security review; they are due before the next release.
Quickstart

Running in five minutes.

Python 3.12 and uv. Pick the drivers you need: pg, mysql, clickhouse, oracle, mssql, db2.

1

Install

Nothing is fetched at runtime. For isolated networks, build a signed offline bundle instead.

$ git clone <repository-url> universal-db-mcp
$ cd universal-db-mcp
$ uv venv --python 3.12 .venv
$ uv pip install --python .venv/bin/python \
    -e '.[pg,mysql]'
2

Add a connection

An interactive wizard that tests the connection live. Credentials go to 0600 files, never into the config. No database handy? Build the demo SQLite file first and point the wizard at it.

$ .venv/bin/udbmcp add-connection
# no database handy? build the demo first
$ .venv/bin/python examples/sqlite-demo/create_demo.py
3

Connect your agent

Detects Claude Code, Claude Desktop, Cursor, VS Code and Cline, and asks before writing any config. Restart the agent and you're in.

$ .venv/bin/udbmcp configure-agents
Then ask: List my databases, then review the largest schema and document it. Air-gapped install guide
FAQ

Questions a DBA would ask.

Can it write to my database, even by accident?

Not through the server. There is no write mode: the configuration rejects security.allow_write_operations: true, security.read_only: false and a connection's read_only: false, no tool writes your data, and every statement an agent sends goes through the guard, which only accepts reads. On engines with a server-side switch, the session refuses writes as well. Query plans are the one thing written, and never into your tables: on Db2, db_explain has EXPLAIN PLAN write plan rows into the explain tables a DBA provisions and deletes them again, and on Oracle it writes to the session-private PLAN_TABLE and deletes its rows. The statement being planned has passed the guard and is never executed. One caveat: in stdio mode the server runs as your user, so an agent that can also run shell commands could read the stored credentials and connect on its own. Give each connection a SELECT-only login (on Db2, plus INSERT, SELECT and DELETE on the explain tables if you want plans), or run the server over HTTP under a service account.

Why not just give the agent a read-only database user?

Do that too. A read-only account stops writes, but it doesn't stop a long scan from taking share locks, cap how much data comes back, mask sensitive columns, keep the agent to the schemas you chose when the account can see more, or leave an audit record per call. The server does all of that.

Does my data leave my network?

Not because of this server. It never calls an LLM, has no telemetry and downloads nothing at runtime. Query results go to the MCP client you connected, and that client decides what reaches its model.

Which agents does it work with?

Any MCP client over stdio or streamable HTTP. udbmcp configure-agents registers it with Claude Code, Claude Desktop, Cursor, VS Code and Cline, and asks before it writes any config file.

What do I need on the database side?

A SELECT-only login that can read the schemas you list (on SQL Server, db_datareader plus SHOWPLAN, never db_owner). Nothing is installed on the server. SQL Server needs Microsoft ODBC Driver 18 on the machine running Universal DB MCP, Oracle thick mode needs Instant Client, Db2 query plans need explain tables a DBA creates once, with INSERT, SELECT and DELETE on them for the login, and a ClickHouse account needs a settings profile with max_memory_usage, the only bound on the server's own memory.

Does masking stop an agent from learning a sensitive value?

It stops the value coming back in a result, wherever the statement moves it: aliases, CTEs, UNIONs, joins and derived tables are traced, and a statement whose shape can't be checked is refused before it runs. Views that show other sessions' SQL (process lists, statement caches, audit trails) are refused on every connection, whatever system schemas you open, because their statement text and bind values would carry what masking hides. So are column statistics (histograms, low and high values) and stored credentials. It doesn't stop inference: a WHERE, ORDER BY or GROUP BY on a masked column can still reveal what it holds. For columns that must stay secret, use column-level grants or a view without them. Masking is the second line.

How do I run it for a whole team?

Install the .deb or .pkg, which sets it up as a service, and serve it over HTTP with a bearer token behind your TLS proxy. Database credentials then live on that host, never on the developers' machines.

Is it production-ready?

It is tested against 26 server images, 12,548 automated tests and no-network package gates for Ubuntu and macOS, and it has been through separate correctness and security reviews; the ledger records what the latest review's 95 findings changed and what stays open. The Windows installer isn't verified yet, the version matrix and package gates are due a re-run on this build, and the ledger lists everything else that isn't verified.

Read everything.Change nothing.

Give your agents the whole picture of your data, without handing them the keys.