Platform Engineering3 November 20266 min read

MCP in Production — Part 4 of 7

The database tool is different

A tool that runs queries against a database is not like a tool that lists resources. It can be destructive, it is not idempotent, and the only boundary that really holds is the database login's permissions. Part 4 of MCP in Production sets out how to give an agent database access without handing it a loaded query editor.

#MCP#AI Agents#Databases#Least Privilege#Platform Engineering

Most tools on a cloud MCP server do something narrow. List the backup vaults. Get the metrics for this resource. Summarise these recommendations. Even the tools that change things usually change one kind of thing in one way. A database query tool is different. Its input is a query, and a query can do almost anything the database login is allowed to do.

Microsoft's release post for Azure MCP Server 3.0 is unusually blunt about this. It describes the MySQL and PostgreSQL query tools as "destructive, non-idempotent, and non-read-only", notes that SQL-verb filtering was removed, and tells users to apply least-privilege database permissions. It also notes that in the server's consolidated mode these tools move from get_azure_databases_details to execute_azure_database_query, a name that finally says what they do. That is a vendor telling you, in writing, that the server is not the safety boundary for database work.

This part explains why database tools need stronger boundaries than resource discovery, and what those boundaries look like.

Why a query is not like a call

Three properties make database tools a separate category.

The input is a programme. A resource tool takes a resource name and returns a description. A query tool takes text that the database will execute. The space of possible actions is the whole query language, limited only by the login's rights.

It is not idempotent. Calling "list vaults" twice returns the same list. Running an UPDATE twice may apply it twice. Running a DELETE after a retry may remove rows that arrived in between. Agents retry; networks time out; an accepted call is not a completed change. Every one of those behaviours is dangerous against a database.

The boundary sits in a different place. For most Azure tools, the boundary is the identity's Azure role assignments. For a query tool, the decisive boundary is the database login and its grants. A Reader role in Azure says nothing about whether the database user can drop a table. Microsoft's post makes the same point: the signed-in database permissions decide what operations are allowed.

For a database tool, the login's grants are the boundaryLAYERWHAT IT ACHIEVESPROMPT OR DESCRIPTION"Only run SELECT"Makes a bad query less likelyVERB FILTERINGBlock DROP, DELETERemoved in 3.0; easily bypassedDATABASE LOGINSELECT on 4 views onlyThe boundary that holdsDATABASE LIMITSStatement timeout, bounded viewsStops runaway queriesDATA COPYMasked, disposableNothing sensitive to take
Layers between an agent's generated query and Sedgefield Freight's data (fictional). The top two shape behaviour but cannot be relied on; Microsoft describes the Azure MCP Server MySQL and PostgreSQL query tools as destructive, non-idempotent and non-read-only, and removed SQL-verb filtering in 3.0. The bottom three hold whatever the model writes.

Filtering is not a boundary

The natural response is to filter the query: block DROP, DELETE and UPDATE before they reach the database. Microsoft removed SQL-verb filtering from these tools in 3.0, and that is a reasonable signal about how far such filtering can be trusted. SQL has many ways to change data: functions with side effects, stored procedures, SELECT ... INTO, statements chained after a comment, vendor-specific commands. Pattern-matching on verbs catches the obvious cases and misses the rest.

The same applies to instructions in prompts or tool descriptions ("only run SELECT statements"). They make a bad query less likely. They do not make it impossible. The preventing expensive and destructive tool calls article explains why layered controls beat a single gate; for databases, the layer that actually holds is the database's own permission system.

The boundaries that hold

Sedgefield Freight is fictional, and its systems and figures are illustrative. Sedgefield's developers want the assistant to answer questions such as "how many consignments were delayed at the Gqeberha depot last week?" against an operational PostgreSQL database. The platform team's design:

BoundarySedgefield's choice
Which databaseA disposable copy refreshed nightly from a masked extract, never the production primary
Which loginA dedicated role with SELECT on four reporting views only; no table access, no functions with side effects
Which dataViews exclude driver and customer contact details; masking happens in the extract, not in the prompt
Which limitsstatement_timeout set on the role, and views that return bounded result sets, so a runaway query stops at the database
Which toolsOnly the query tool for this workflow is exposed; administrative database tools are not
What is recordedEvery query text, the identity, the row count and the duration, kept with the conversation reference

None of this depends on the model behaving well. If the agent generates an UPDATE, the database refuses it. If it generates a query that scans everything, the timeout ends it. If a prompt injection in returned data asks it to export the customer table, the customer table is not there.

Prove the boundary by trying to break it

A design table is a claim. Before the tool is enabled, someone should log in as the agent's database role, not as an administrator, and run a short set of statements that must fail or stop:

AttemptExpected resultWhat a pass proves
UPDATE or DELETE against a reporting viewPermission deniedThe role has no write grants, even on updatable views
CREATE TABLE in the default schemaPermission deniedThe role cannot create objects; on PostgreSQL versions before 15, every role could create in the public schema by default unless that was revoked
SELECT from an underlying tablePermission deniedAccess is limited to the views
SELECT pg_sleep(600)Cancelled at the statement timeoutLimits are enforced by the database
Calling a function the views do not usePermission denied, or the function has no side effectsSide-effecting code is out of reach; PostgreSQL grants EXECUTE on new functions to everyone by default, so this usually needs an explicit revoke

Two common shortcuts fail this test. Setting default_transaction_read_only on the role looks like a boundary, but a session can switch it off again with a single SET command, so it only protects against accidents. And a role that inherits from a broader group role can carry grants nobody remembers giving; check effective privileges, not just the grants made directly to the agent's role.

Record the results with the date and the database version, and rerun them whenever the grants, the views or the database version change. If one of the attempts succeeds, the tool is not ready, however well the agent has behaved in testing.

When write access is genuinely needed

Some workflows will need an agent to change data: correcting a reference, closing a stuck job, reprocessing a record. Treat these as change workflows, not query workflows.

  • Use a narrow interface, not a query tool. A stored procedure or a small API that performs one named action with validated parameters is far safer than free-form SQL. It can be idempotent by design, for example by requiring an operation identifier and refusing duplicates. The design an MCP server as a product article covers this pattern.
  • Prepare, then approve. The agent proposes the exact statement or action, with the rows it will affect. A person approves that exact change, not a summary of it.
  • Execute under a different identity. The identity that can write is not the identity that explores. It is scoped to the procedure, not the schema.
  • Keep the evidence. The proposal, the approval, the executed statement, the affected row count and the result belong in one record. Part 7 describes it.

Never connect an agent's query tool to production with credentials that can write. That is not an agent design; it is an open console.

A checklist before any database tool is enabled

  1. Is the target a disposable or masked copy? If not, why not, and who approved it?
  2. Which database login does the tool use, and what are its exact grants?
  3. Are timeouts and result sizes bounded by the database and its views, not the prompt?
  4. Is personal information excluded at source, not filtered in the response?
  5. Are the query texts and results metadata logged with the requesting user?
  6. Is any write path a named, validated, idempotent action with approval?
  7. Who owns the login, and when will its grants next be reviewed?

If any answer is "the agent knows not to", the tool is not ready.

Next: Part 5, An MCP upgrade is a production change. All parts are on the series page.

How CloudNala can help

CloudNala helps teams give agents database access safely. We design the masked or disposable data copies, the narrow database roles and views, the limits enforced at the database, and the named write actions that replace free-form queries where change is genuinely needed. We test the design by trying to make the agent do the wrong thing, before your users do.


Work with CloudNala

CloudNala helps organisations move from technology ambition to practical execution across cloud, AI, data, platform engineering and digital services.

Whether you are exploring AI, modernising your cloud environment, building a public-sector digital service, or turning an idea into a working MVP, we can help you shape the roadmap and deliver the next step.

Book an MCP Readiness Review or write to us at consult@cloudnala.co.za