AI Agents Have Started Reading Databases Directly
MCP (Model Context Protocol) is an open standard that aims to unify how AI tools connect to external data and systems. Anthropic released it on November 25, 2024, and on December 9, 2025 it was donated to the Agentic AI Foundation under the Linux Foundation. Once you attach an MCP server for databases, an AI agent can look up the list of tables and their columns and execute SQL directly.
This shifts the design perspective. Human developers can read the internal wiki or ask a colleague, but what an agent sees is mostly the database catalog: table names, column names and types, constraints, and comments. The schema itself has to serve as the documentation, or the agent may join the wrong tables or misread units.
This post covers five principles, good for humans but even more important for agents, using PostgreSQL as the reference.
1. Use Meaningful Names, Not Abbreviations
Agents interpret abbreviations like usr_amt_cd by guessing. Guesses can be wrong.
-- Names to avoid: abbreviations, no units, meaningless flags
CREATE TABLE ord (
id BIGINT PRIMARY KEY,
usr BIGINT,
amt NUMERIC,
flg CHAR(1),
dt TIMESTAMP
);
-- Recommended: meaning, units, and time zone show up in names and types
CREATE TABLE orders (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
customer_id BIGINT NOT NULL,
amount_krw NUMERIC(12,0) NOT NULL,
is_refunded BOOLEAN NOT NULL DEFAULT false,
ordered_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
- Put units and currency in the name:
amount_krw,duration_ms,weight_g - Prefix booleans with
is_orhas_:is_refundedinstead offlg - Use the same name for the same concept in every table: if one place uses
user_idand another usesmember_no, the agent can't tell whether they are the same thing - Use
TIMESTAMPTZfor points in time: values without a time zone are open to different interpretations, by humans and agents alike
2. Record Meaning in the Catalog with COMMENT ON
PostgreSQL's COMMENT ON stores descriptions in the catalog (pg_description). Unlike code comments or a wiki, these descriptions travel with the database, so any tool that inspects the schema can read them through the same path.
COMMENT ON TABLE orders IS 'Customer order. Created when payment completes; the row is not deleted on cancellation (marked with is_refunded).';
COMMENT ON COLUMN orders.amount_krw IS 'Payment amount (KRW, VAT included). Amount after discounts are applied.';
COMMENT ON COLUMN orders.is_refunded IS 'Whether the order was fully refunded. For partial refunds, see the refunds table.';
You can query the stored descriptions like this:
SELECT a.attname AS column_name,
format_type(a.atttypid, a.atttypmod) AS data_type,
col_description(a.attrelid, a.attnum) AS description
FROM pg_attribute a
WHERE a.attrelid = 'orders'::regclass
AND a.attnum > 0
AND NOT a.attisdropped;
What should you write?
- The unit and scope of values (whether VAT is included, before or after discounts)
- The meaning of status values and their transition rules
- Whether soft deletion is used, and the conditions for a row that is "actually valid"
- Caveats in relationships with other tables (e.g., partial refunds are in another table)
3. Express Rules as Constraints: Agents Can't See Application Code
If the rule "this column is one of pending, paid, or shipped" exists only in application code, it is a rule that doesn't exist as far as the agent is concerned. Declared as a constraint, it can be read from the catalog, and it becomes the last line of defense: the database rejects invalid values even when someone tries to write them.
CREATE TABLE customers (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
email TEXT NOT NULL UNIQUE
);
CREATE TABLE shipments (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
order_id BIGINT NOT NULL REFERENCES orders (id),
status TEXT NOT NULL
CHECK (status IN ('pending', 'paid', 'shipped', 'delivered')),
shipped_at TIMESTAMPTZ,
CHECK (status <> 'shipped' OR shipped_at IS NOT NULL)
);
- Declaring foreign keys explicitly lets the agent confirm join paths in the catalog instead of guessing them
- CHECK exposes allowed values and rules between columns
- NOT NULL and UNIQUE convey the facts "always present" and "only one"
4. Organize What the Agent Sees with Views
If you leave the agent to compute "monthly revenue" from the raw tables every time, whether refunds are included or how time zones are handled can differ from one question to the next. Fix the agreed-upon definition in a single view and attach a description, and the agent can just use that view.
CREATE VIEW monthly_revenue AS
SELECT date_trunc('month', ordered_at AT TIME ZONE 'Asia/Seoul') AS month_kst,
sum(amount_krw) AS revenue_krw,
count(*) AS order_count
FROM orders
WHERE NOT is_refunded
GROUP BY 1;
COMMENT ON VIEW monthly_revenue IS 'Monthly revenue (KRW). Months in Korea Standard Time; fully refunded orders excluded.';
5. Least Privilege: GRANT Is the Boundary, the Rest Is a Safety Net
Create a separate role for the agent's account and grant it only read access to the schemas it needs.
CREATE ROLE ai_reader LOGIN; -- Set the password and authentication method according to your production environment's policy
GRANT USAGE ON SCHEMA analytics TO ai_reader;
GRANT SELECT ON ALL TABLES IN SCHEMA analytics TO ai_reader;
ALTER DEFAULT PRIVILEGES IN SCHEMA analytics GRANT SELECT ON TABLES TO ai_reader;
-- Defaults to prevent mistakes (not a security boundary)
ALTER ROLE ai_reader SET default_transaction_read_only = on;
ALTER ROLE ai_reader SET statement_timeout = '15s';
There is one caveat. default_transaction_read_only and statement_timeout are settings the user can change back within a session. They reduce the chance of accidentally sending a write query or of a heavy query running for a long time, but they don't prevent either. The real boundary is the privileges granted with GRANT.
- It is safer to leave personal data columns out of the views in the agent's schema entirely
- If you need to scope access row by row, use
ALTER TABLE ... ENABLE ROW LEVEL SECURITYwith policies (CREATE POLICY) - Connecting to a read replica instead of the production primary reduces the impact of heavy analytical queries on your service
Designing with Erdmates
When you write descriptions for tables and columns in Erdmates, they are included as COMMENT ON TABLE and COMMENT ON COLUMN statements when you export PostgreSQL DDL (for MySQL, a table COMMENT). Conversely, when you import SQL from an existing database, Erdmates reads the COMMENT ON statements and restores them as descriptions in the ERD. If you refine names and descriptions while checking foreign key relationships as lines in the ERD, humans and agents end up looking at the same documentation.
Conclusion
Schema design in an era when agents work with databases directly is not a new technology; it is following old principles more strictly. Names that reveal meaning, descriptions that stay in the catalog, rules declared as constraints, views that hold agreed-upon definitions, and least privilege. A schema with these five makes agents less likely to get things wrong, and even when they do, the database stops them.