Back to blog

The MCP Security Checklist I Run Before Connecting Any Database

Spencer Pauly
Spencer Pauly
5 min read
The MCP Security Checklist I Run Before Connecting Any Database

Before I let any MCP server touch a database that matters, I run the same mental checklist. It's not long, and most of it is the kind of thing you know but skip because the happy path works without it. Writing it down so you can steal it.

This applies whether you're using an off-the-shelf MCP server, an open-source one, or something you built. The questions are about properties, not products.

1. Can it write? Prove that it can't.

The first and most important check. Not "is it configured read-only" but "have I confirmed it can't write." These are different. A config flag can be wrong, ignored, or bypassed.

The real test is to try. Ask the agent to insert a row, update a record, drop a table. If your setup is right, it comes back and says it can't, because the database role genuinely lacks the grant or the layer rejected the query at parse time. If it succeeds, or if it "decides not to" rather than being unable to, you don't have read-only. You have a polite agent, which is not a security control.

Enforce read-only at the database role level, the query-parsing level, or both. Never at the prompt level alone.

2. Where does the credential live?

Find the connection string. Physically locate it. Is it in a plaintext JSON config file in someone's home directory? Is it in an environment variable that every process can read? Is it synced to a dotfiles repo?

If a database credential is sitting in plaintext somewhere a screen-share or a compromised dependency could reach it, that's the hole, regardless of how good the rest of the setup is. The credential is the keys to the building. Treat it like one. Prefer a setup where the raw credential is held by a managed layer and never lands in a file on a developer's laptop.

3. What's the blast radius if a query goes wrong?

Read-only protects your rows. It does nothing for the database itself. So check the three controls that protect the server:

A statement timeout, so a slow or runaway query gets killed instead of pinning your primary. A row cap, so a wide SELECT can't return half a million records and either flood the agent's context or hammer your I/O. A connection limit, so an agent retrying in a loop can't exhaust your pool. Without these, a perfectly read-only agent can still take your database down with one bad join, which I've written about at length in read-only doesn't stop Postgres.

4. Can it see data it shouldn't?

Read access is not all-or-nothing, and it shouldn't be. Check what the agent can actually see.

Can it read your users' PII when the questions it answers never need names or emails? Restrict columns, or point it at a view that excludes the sensitive fields. Can it read rows outside its scope — other tenants, internal test accounts, other regions? Use row-level security to fence it in. The goal is that even a fully compromised agent session can only reach the data you intended to expose, not everything in the table.

5. Is every query logged, including the blocked ones?

When something goes wrong, the log is the only thing that tells you what happened. Confirm one exists before you need it.

The successful queries matter, but the blocked ones matter more, because a spike in rejected queries is often your first sign of a prompt-injection attempt or a misconfigured agent. If your MCP server runs queries and keeps no record, you're flying blind, and the first incident will be the one where you wish you'd checked this box. Log the SQL, the timestamp, the session, the row count, the duration, and whether it was allowed.

6. What's in the model's context that could become a query?

This is the subtle one. If the agent's context includes data from your database — a support ticket body, a user's display name, a row someone else wrote — then that data can influence the query the model generates. That's the prompt-injection-via-data path, and it's real. The defense is the same as check 1: the query gets validated before it runs, so a malicious string in your data can't turn into a malicious query that executes. If your only defense is "the model probably won't be tricked," that's not a defense.

Running the list

None of these are exotic. They're the boring fundamentals, and the reason I keep a checklist is that boring fundamentals are exactly what you skip when the demo works and you're excited to ship. I built QueryBear so that most of this list is handled by default rather than by my remembering to check it, but the list is the point, not the product. Run it against whatever you use. If you can't answer all six, you're not ready to point it at production yet.

Database Access

Give Your AI Agents
Database Access. Securely.

Connect any database. Control permissions. Audit every query. All running locally on your machine.