{"slug": "why-read-only-mcp-is-not-read-only-until-the-database-agrees", "title": "Why Read-Only MCP Is Not Read-Only Until the Database Agrees", "summary": "Read-only access for AI agents connected to production databases must be enforced at both the MCP layer and the database layer, according to a new security guide. The guide recommends creating a dedicated non-owner database role with minimal permissions, testing that prohibited writes fail, and regularly reviewing effective privileges to prevent policy mistakes from becoming production changes.", "body_md": "TL;DR — Key Takeaways\n\n- Enforce read-only access at both the MCP layer and the database.\n- Use a dedicated, non-owner database role with only the permissions the AI agent needs.\n- Test that approved reads succeed and prohibited writes fail.\n- Review inherited roles, public grants and effective privileges regularly.\n- Keep MCP credential revocation and database access revocation independent.\n- Correlate MCP request logs with database execution logs for stronger auditing.\n\nTeams connecting AI agents to production data often start with a reassuring control: Mark the MCP server or tool as read-only. The label is useful, but it is not the final security boundary. A database connection with broad privileges can still turn a policy mistake into a production change.\n\nThe safer model is simple: Read-only access should be enforced twice. The MCP layer narrows what the client is allowed to request. The database independently narrows what the execution identity is allowed to do.\n\n### Read-Only is a Chain, Not a Switch\n\nThere are at least three permission layers in a typical MCP database workflow:\n\n- The AI client proves its identity to the MCP server.\n- The MCP server decides which tools and operations the client may use.\n- The database authorizes the role used to execute the final query.\n\nThe effective permission is the intersection of all three layers. Authentication answers who is calling. MCP policy answers which operation the caller may request. Database grants answer what the resulting session can actually execute.\n\n### Why the Database Must Have the Final Say\n\nTool-level restrictions are application controls. They depend on the server correctly classifying operations and applying policy to every path that can reach the database.\n\nThat control can fail in ordinary ways. A new tool may bypass an older permission check. A parser may classify a statement differently than expected. A stored procedure exposed as a read operation may produce side effects.\n\nNone of these possibilities means the MCP layer is useless. It means the database should not delegate its final authorization decision to another component.\n\nIf the database role can only connect, use a specific schema and select from approved tables, then an attempted write fails even if the request reaches the database.\n\n### Create a Dedicated Execution Identity\n\nDo not connect an AI agent with an owner, migration or administrator account. Create a separate login role for the workload and grant it access to only the objects needed.\n\nFor PostgreSQL, a minimal pattern can look like this:\n\n```\nCREATE ROLE ai_reader LOGIN PASSWORD 'replace-with-a-secret';\n\nGRANT CONNECT ON DATABASE app_production TO ai_reader;\nGRANT USAGE ON SCHEMA reporting TO ai_reader;\nGRANT SELECT ON TABLE\n  reporting.daily_metrics,\n  reporting.customer_health\nTO ai_reader;\n```\n\nGranting access to selected tables makes the review surface smaller and prevents a newly created sensitive table from becoming readable by accident. Broader schema access may be appropriate, but it should be a documented decision.\n\n### Test Denial, Not Only Success\n\nMost connection checks prove that a query works. A security check must also prove that a forbidden query fails.\n\nAfter configuring the execution identity, run one expected read and one harmless write attempt in a controlled test object or within a transaction:\n\n```\nSELECT * FROM reporting.daily_metrics LIMIT 1;\n\nBEGIN;\nUPDATE reporting.daily_metrics\nSET metric_value = metric_value\nWHERE false;\nROLLBACK;\n```\n\nThe read should succeed. The update should be rejected because the role lacks the`UPDATE`\n\n, even though the statement would not change a row.\n\nThis test catches a common configuration error: The MCP interface says read-only, but the database credential belongs to a role with write access. Repeat it after permission changes, migrations, credential rotation or MCP server upgrades.\n\n### Check Effective Privileges\n\nSecurity reviews should inspect effective access, not only the grants someone remembers creating. Roles can inherit permissions through memberships, ownership, default privileges or public grants.\n\nIn PostgreSQL, teams can review role memberships and object privileges with catalog queries and `information_schema`\n\nviews. The review should answer:\n\n- Can the role create objects in any reachable schema?\n- Can it insert, update, delete, truncate or execute privileged functions?\n- Does it inherit access from another role?\n- Can it read tables outside the intended scope?\n- Can it change its own role or session authorization?\n\nRun these checks using the same login identity and connection path used by the MCP server. An administrator session can hide differences in inherited privileges and row-level security behavior.\n\n### Keep Revocation Independent\n\nIncident response should not depend on a single control plane. Teams should be able to disable the MCP credential and revoke the database role separately.\n\nAt the MCP layer, revoke the client key, OAuth grant or server link. At the database layer, block new sessions and terminate existing ones if the situation requires immediate containment.\n\nFor PostgreSQL, a basic emergency action can start with:\n\n```\nALTER ROLE ai_reader NOLOGIN;\n```\n\nThat prevents new connections. Existing sessions may need to be terminated separately. Document and test the procedure before an incident.\n\n### Log Both the Request and the Execution\n\nMCP activity logs and database logs answer different questions.\n\nThe MCP layer can record the authenticated client, requested tool, policy decision, connection and response status. The database can record the execution identity, statement, duration and server-side error. Correlation identifiers, timestamps and stable connection identifiers help connect the two views.\n\n### A Practical Production Checklist\n\nBefore giving an AI agent access to a production database, verify the following:\n\n- The MCP client has its own revocable identity.\n- The MCP policy exposes only the required tools and operations.\n- The database uses a dedicated non-owner login role.\n- The role has access only to the required schemas, tables and operations.\n- One allowed read succeeds through the real MCP path.\n- One forbidden write fails at the database boundary.\n- Effective privileges and inherited role memberships are reviewed.\n- MCP and database activity can be correlated.\n- Gateway revocation and database revocation are documented separately.\n\nA read-only label is useful because it communicates intent and reduces the operations available to the client. Production safety comes from turning that intent into independent enforcement. When both the MCP layer and the database agree, a mistake in one layer is less likely to become a change in production data.", "url": "https://wpnews.pro/news/why-read-only-mcp-is-not-read-only-until-the-database-agrees", "canonical_source": "https://techstrong.ai/features/why-read-only-mcp-is-not-read-only-until-the-database-agrees/", "published_at": "2026-08-06 08:18:15+00:00", "updated_at": "2026-08-09 13:09:20.793122+00:00", "lang": "en", "topics": ["ai-safety", "ai-policy", "ai-infrastructure"], "entities": ["MCP", "PostgreSQL"], "alternates": {"html": "https://wpnews.pro/news/why-read-only-mcp-is-not-read-only-until-the-database-agrees", "markdown": "https://wpnews.pro/news/why-read-only-mcp-is-not-read-only-until-the-database-agrees.md", "text": "https://wpnews.pro/news/why-read-only-mcp-is-not-read-only-until-the-database-agrees.txt", "jsonld": "https://wpnews.pro/news/why-read-only-mcp-is-not-read-only-until-the-database-agrees.jsonld"}}