Database MCP Servers: Letting an Agent Query Postgres or MySQL Safely

Which database MCP servers exist today, from Supabase, Neon, Google and Microsoft, how they are set up, and the controls that make them safe: a read-only database login, a replica, row and time limits, no production writes, and treating what comes back as untrusted.

8 min read

A database MCP server gives an AI assistant tools to list tables, read schemas and run SQL against a database you connect. Used safely, it points at a copy or read replica rather than the production primary, logs in as a database user that can only SELECT from the tables the job needs, has a time limit and a row cap on every query, cannot write, and asks a person before each call. The one control that holds when everything else fails is the database login’s own permissions, so set those first. A server’s read-only switch is a second layer, not the first.

A worked example of asking a development database a question sits among the MCP examples, and the general rules for any server, from trust lists to revocation, are in MCP security best practices. This piece is about databases in particular.

The servers you will meet

  • Supabase: a hosted server at https://mcp.supabase.com/mcp. According to Supabase’s MCP guide (opens in a new tab), read_only=true runs every query as a read-only Postgres user, project_ref scopes it to one project, and features limits which tool groups load.
  • Neon: a hosted server at https://mcp.neon.tech/mcp with OAuth or an API key, and a readonly=true option. Neon recommends it for development and testing only, and its migration tools apply schema changes on a temporary branch first.
  • Google’s MCP Toolbox for Databases: an open-source server under the Apache 2.0 licence that connects to Postgres, MySQL, SQL Server, AlloyDB, Cloud SQL, Spanner, BigQuery and many others. Its prebuilt configurations give generic list_tables and execute_sql tools; its custom tools (opens in a new tab) let you define fixed, parameterised queries in tools.yaml instead.
  • Microsoft: the Azure MCP Server has tools for Azure Database for PostgreSQL and MySQL, including a query tool that runs the SQL you give it, signing in with Microsoft Entra ID or a database password. Separately, SQL MCP Server (opens in a new tab), part of the open-source Data API builder, deliberately does not let the model write SQL: it exposes only the tables, views and procedures you configure, with per-role permissions.
  • The old reference Postgres server: the MCP project archived it, and it is no longer maintained. It wrapped queries in a read-only transaction, which Datadog Security Labs showed (opens in a new tab) could be escaped by sending COMMIT; followed by any other statement. Do not use it.

Many community servers exist for Postgres and MySQL. Judge them the same way: what login they use, whether they accept several statements in one call, and whether read-only is enforced by the database or only by the server.

Setup, in outline

Every database MCP server needs the same three things: where the database is, which login to use, and which tools to expose. Hosted servers take the first two from an OAuth sign-in and a project reference. Local servers take a connection string or separate variables. Here is a MySQL database connected to Cursor through Google’s Toolbox, logging in as the restricted user created below, and the same idea for Supabase in Claude Code.

.cursor/mcp.json: MySQL through MCP Toolbox
{
  "mcpServers": {
    "mysql": {
      "command": "./PATH/TO/toolbox",
      "args": ["--prebuilt", "mysql", "--stdio"],
      "env": {
        "MYSQL_HOST": "replica.internal",
        "MYSQL_PORT": "3306",
        "MYSQL_DATABASE": "app",
        "MYSQL_USER": "agent_ro",
        "MYSQL_PASSWORD": "<from your secret store>"
      }
    }
  }
}
Terminal: Supabase in Claude Code, one project, read-only
claude mcp add --transport http supabase \
  "https://mcp.supabase.com/mcp?project_ref=<dev-project-ref>&read_only=true"

Keep the password out of any file you commit. Toolbox’s prebuilt execute_sql is described as executing any SQL statement, which is exactly why the login it uses matters more than the tool.

1. A read-only database login

Create a login for the assistant and grant it SELECT on the tables it needs, and nothing else. In PostgreSQL, the GRANT documentation (opens in a new tab) covers database, schema, table and column privileges; column grants let you leave out email addresses, tokens and hashes entirely.

PostgreSQL
CREATE ROLE agent_ro LOGIN PASSWORD '<generated>' CONNECTION LIMIT 2;
GRANT CONNECT ON DATABASE app TO agent_ro;
GRANT USAGE ON SCHEMA public TO agent_ro;
GRANT SELECT ON public.orders, public.products TO agent_ro;
GRANT SELECT (id, created_at, plan, country) ON public.accounts TO agent_ro;

-- defaults for each session, not a boundary: see below
ALTER ROLE agent_ro SET default_transaction_read_only = on;
ALTER ROLE agent_ro SET statement_timeout = '5s';
ALTER ROLE agent_ro SET idle_in_transaction_session_timeout = '10s';
MySQL 8.4
CREATE USER 'agent_ro'@'10.0.%' IDENTIFIED BY '<generated>'
  WITH MAX_USER_CONNECTIONS 2;
GRANT SELECT ON app.orders TO 'agent_ro'@'10.0.%';
GRANT SELECT ON app.products TO 'agent_ro'@'10.0.%';
GRANT SELECT (id, created_at, plan, country) ON app.accounts TO 'agent_ro'@'10.0.%';
  • The grants are the boundary. PostgreSQL documents that default_transaction_read_only only sets the default for new transactions, so a session can change it. The login has no write privileges anyway, which is what actually stops a write.
  • Avoid the shortcut roles. PostgreSQL’s pg_read_all_data reads every table, view and sequence in every schema. That is the opposite of a narrow grant.
  • Row-level security still applies to this login unless it has BYPASSRLS, so on Supabase-style schemas the assistant sees only what its policies allow.
  • One login per assistant or job, never shared with the application or a person, so the database’s own logs say who ran what.

2. A replica or a copy, not the primary

Point the server at something whose failure costs nothing. A PostgreSQL hot standby accepts only read-only connections, not even temporary tables can be written, so it adds a second wall behind the grants. A MySQL replica with super_read_only on refuses writes even from administrative accounts. Better still for exploration is a development branch or a masked copy: Supabase and Neon both point assistants at branches for this reason. A heavy query on a standby can also be cancelled when it conflicts with replication, which is the right way round.

3. Row and time limits

A model that writes SELECT * FROM events will happily pull a hundred million rows into its context window. Set limits in three places. In the database: a statement_timeout per role in PostgreSQL, and max_execution_time in MySQL, which applies to read-only SELECT statements; both are session settings a determined caller can change, so treat them as defaults. In the server: prefer servers or custom tools that add a row cap and their own timeout. In the connection: a connection limit per login, as in the grants above, so one runaway loop cannot exhaust the pool.

4. No production writes through a chat

Schema changes and data fixes should reach production the way code does: written as a migration or script, reviewed, and applied by your deployment process. An assistant can draft the migration and test it on a branch; it should not run it on the primary through a tool call. If a job genuinely needs to write, give it a separate login with INSERT or UPDATE on one table, keep the host’s approval prompt on for every call, and write down who owns that decision.

5. SQL injection through the model

When the model writes SQL, anything that influences the model influences the query. OWASP’s entry on improper output handling (opens in a new tab) in its 2025 LLM Top 10 lists model-generated SQL run without parameterisation as a direct route to injection, and says to treat the model like any other untrusted user. Two defences follow. Prefer servers that run one statement per call, since the archived Postgres server fell to a stacked COMMIT. And for repeated jobs, replace free-form SQL with fixed queries whose only inputs are typed parameters, such as Toolbox custom tools or Data API builder entities.

6. Prompt injection hiding in the rows

The data itself can carry instructions. Supabase’s own example is a customer support ticket whose text instructs the assistant to run queries it should not and reveal what it finds. If the same session can reach a tool that writes, sends or publishes, a row becomes a command. Keep database sessions free of outbound tools where you can, keep approval prompts on, and read indirect prompt injection for the full pattern.

What never to connect

  • The production primary with the application’s own login, or any owner, admin or superuser account.
  • Tables holding password hashes, session or API tokens, payment details or health data, unless column grants leave those columns out.
  • Any server that asks you to paste a connection string into a config file that is committed or shared.
  • An assistant your customers or end users talk to. Supabase says plainly not to give its MCP server to them.
  • A server whose read-only mode you have not checked is enforced by the database login.

The same idea as a scoped board token

A narrow database login is the database version of what a well-built MCP server does for its own data. On fenbs, an assistant connected over MCP gets a one-hour access token that is renewed with a refresh token, or a token issued by hand in Settings with a name, the scopes read, write and comment, and an optional expiry. Either kind is capped by the role of the person who connected it and can be revoked in Settings. Give the database the same shape: one named login per assistant, the fewest grants, a way to end it, and a log that shows what it did. When a database finding needs follow-up, the assistant can file it as a bug on the board, where History shows it under the assistant’s name.

Related

Before you connect: MCP security risks. Checking every agent grant each quarter: security review of AI agent access. How scoped tokens work on a board: assistant tokens and scopes and the MCP docs.

Questions people ask.

Is there an official Postgres MCP server?

The MCP project’s reference Postgres server is archived and should not be used; a researcher showed its read-only mode could be bypassed. Use a maintained server from your database provider, such as Supabase or Neon, or an open-source option such as Google’s MCP Toolbox for Databases, with a read-only database login.

How do I connect Cursor to MySQL with MCP?

Add a server entry to .cursor/mcp.json that starts a MySQL-capable MCP server, such as Google’s MCP Toolbox with its prebuilt mysql configuration, and pass the host, database, user and password as environment variables. Use a dedicated user with SELECT on only the tables you need, and keep the password out of committed files.

Is a server’s read-only mode enough?

No. Treat it as a second layer. The login the server uses should have no write privileges at all, so that a bug in the server or a stacked statement still cannot change data.

Should an AI agent query a production database?

Prefer a development branch, a masked copy or a read replica. If production evidence is genuinely needed, use a read-only login with column-level grants, time limits and approval for every call, and remove the access when the job is done.

Start with one thing.

There is nothing to set up first. Write one line and you’ve started.