MP-301d · Module 1
Read-Only Connections & Injection Prevention
3 min read
A read-only database connection is the foundation of query safety. Create a dedicated database role with SELECT-only grants on the specific tables and views the MCP server needs. Revoke all other privileges — INSERT, UPDATE, DELETE, CREATE, DROP, TRUNCATE. Then set the connection to read-only mode at the session level (SET default_transaction_read_only = ON in PostgreSQL) as a second guardrail. This way, even if a SQL injection attack constructs a write statement, the database itself rejects it.
Connection-level read-only mode and role-level grants serve different purposes. Role grants determine which objects the user can access. Session-level read-only mode prevents any write operation regardless of grants. Use both: the role limits which tables are visible, and the session setting prevents writes even if the role accidentally has write permissions. Some database drivers also support a readOnly connection option that sets this automatically — use it as a third layer.
Beyond SQL injection, consider indirect write vectors. Certain PostgreSQL functions can write to disk (COPY TO, lo_export), create temporary tables, or trigger side effects through function calls. A properly restricted role should not have access to these, but verify by testing: connect as the MCP role and attempt every operation you want to prevent. Automated tests for permission boundaries are just as important as tests for application logic.
-- Create a dedicated read-only role for the MCP server
CREATE ROLE mcp_reader WITH LOGIN PASSWORD 'rotate-this-regularly';
-- Grant connect to the database
GRANT CONNECT ON DATABASE appdb TO mcp_reader;
-- Grant usage on the schema
GRANT USAGE ON SCHEMA public TO mcp_reader;
-- Grant SELECT on specific tables only
GRANT SELECT ON TABLE customers, orders, products, order_items
TO mcp_reader;
-- Explicitly revoke everything else
REVOKE ALL ON ALL TABLES IN SCHEMA public FROM mcp_reader;
GRANT SELECT ON TABLE customers, orders, products, order_items
TO mcp_reader;
-- Set default read-only at the role level
ALTER ROLE mcp_reader SET default_transaction_read_only = ON;
-- Prevent function creation and temp table abuse
REVOKE CREATE ON SCHEMA public FROM mcp_reader;
REVOKE TEMPORARY ON DATABASE appdb FROM mcp_reader;
- Create the role Create a dedicated database role for MCP with SELECT-only grants. Never reuse the application read-write role — even temporarily.
- Set session read-only Configure default_transaction_read_only at the role level. Also set the readOnly flag in your connection pool configuration as a backup.
- Test the boundaries Connect as the MCP role and attempt INSERT, UPDATE, DELETE, CREATE TABLE, COPY, and function calls. Every one should fail. Automate these tests in your CI pipeline.