mcp-postgres-guard
Provides guarded access to a PostgreSQL database with least-privilege scopes, statement analysis, PII masking, human approval for writes, and an audit trail.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@mcp-postgres-guardshow me the users table schema"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
mcp-postgres-guard
An MCP server that gives AI agents access to a PostgreSQL database without giving them the keys to it: least-privilege scopes, statement analysis by a real parser, PII masking, human approval for every write, and an append-only audit trail.
Works with Claude Desktop, Claude Code, or any MCP client.
// claude_desktop_config.json
{
"mcpServers": {
"postgres": {
"command": "npx",
"args": ["-y", "mcp-postgres-guard"],
"env": {
"DATABASE_URL": "postgres://readonly@localhost:5432/app",
"READABLE_TABLES": "customers,orders",
"MASKED_COLUMNS": "ssn,customers.email"
}
}
}
}That configuration is read-only with two tables visible and two columns redacted. Writes require three more variables, deliberately.
The problem
Handing an agent a database connection means handing it everything the connection can do. The usual mitigations do not hold up:
Mitigation | How it fails |
"The prompt says read-only" | Prompts are advisory. A confused agent, or an injected instruction inside a row of data, ignores them |
Regex check for | Defeated by |
A read-only database role | Correct and necessary, but all-or-nothing: it cannot express "may update orders, never customers", nor mask a column, nor ask a human |
Reviewing the agent's SQL afterwards | The row is already gone |
This server addresses each layer separately, so no single check has to be perfect.
Related MCP server: AgenticMCP
What it does
Statements are parsed, not pattern-matched
Every statement goes through a real PostgreSQL parser
before it reaches the database. That yields a structural answer — statement
class, every table touched, whether a mutation carries a WHERE clause —
instead of a guess.
SELECT 1; DROP TABLE users
→ Expected a single statement, found 2. Stacked statements are refused.
SELECT * FROM logs WHERE message = 'DROP TABLE users'
→ allowed. It is a read; the keyword is data.The second case matters as much as the first: a guard that produces false positives gets switched off.
Reads cannot write, twice over
A statement classified as a read still executes inside BEGIN READ ONLY. If
the analyser ever misjudges a statement class, Postgres refuses the write on its
own. Defence that does not depend on this codebase being correct is the only
kind worth relying on here.
search_path is pinned to the exposed schemas, so an unqualified table name
cannot resolve to something the policy check never saw.
Columns can be masked without being hidden
SELECT id, name, email, ssn FROM customers[{ "id": 1, "name": "Ann Lee", "email": "***REDACTED***", "ssn": "***REDACTED***" }]The agent still sees that email exists and can filter on it — queries stay
valid — but the values never leave the process. Masking is applied to result
rows rather than rewritten into the query, because a SELECT can surface a
column under an alias, through *, or via an expression; output filtering
catches all three.
Writes are measured, then approved by a human
A mutation is first executed inside a transaction that is always rolled back, purely to learn how many rows it really touches. Only then is a human asked, through MCP elicitation:
Approve this UPDATE?
Reason given: Customer confirmed delivery address
Rows affected: 1
UPDATE orders SET status = $1 WHERE id = $2The count comes from an actual execution rather than EXPLAIN, because planner
estimates are least reliable on stale statistics — exactly the situation where
an approval prompt earns its keep.
UPDATE/DELETE with no WHERE clause is refused outright rather than sent
for approval. A human scanning a wall of SQL misses a missing WHERE
reliably, and it is the one mistake with no undo.
If the client does not support elicitation, the write is declined. "Nobody could be asked" is not "approved".
Everything is recorded
Every call — allowed, denied, approved, declined, failed — is appended as JSONL with the SQL, the tables, the row count, and the reason:
{"timestamp":"2026-07-28T14:03:11.204Z","tool":"query","outcome":"denied",
"sql":"SELECT * FROM internal_secrets",
"reason":"Table \"internal_secrets\" is not in the readable set."}Denied calls are logged as carefully as successful ones. A log of successful queries alone cannot tell a well-behaved agent from one that probed for a way in and was stopped.
Tools
Tool | Description |
| Tables readable under the policy, with row estimates |
| Columns, types, nullability, defaults; masked columns marked |
| A single |
| A single |
Denials return the reason to the agent. An agent told only "denied" retries the
same query; one told "table secrets is not in the readable set" moves on.
Configuration
Variable | Default | Purpose |
| — | Required |
|
| Schemas visible at all |
| (all) | Readable tables; empty means every table in the allowed schemas |
| (none) | Writable tables; writes are opt-in |
| (none) |
|
|
| Master switch for mutations |
|
| Human confirmation per write |
|
| Refuse a mutation above this blast radius |
|
| Read result cap |
|
| Server-enforced |
|
| Bounds an agent stuck in a retry loop |
| (stderr) | JSONL audit file |
Two configurations are rejected at startup rather than failing confusingly later: writes enabled with no writable tables named, and a table that is writable but not readable — a change that cannot be reviewed cannot be approved.
Try it
npm install
docker run -d --name guard-demo -e POSTGRES_PASSWORD=pw -p 5432:5432 postgres:17
DEMO_DATABASE_URL=postgres://postgres:pw@localhost:5432/postgres \
node --experimental-strip-types scripts/demo.tsscripts/demo.ts connects as a real MCP client and walks through reads,
masking, six kinds of denial, and an approved write — printing exactly what a
connected agent would see.
Or point the MCP Inspector at it:
npm run inspectTesting
npm test # 64 tests
npm run typecheckThe test suite concentrates on the security boundary, because that is where a
bug is expensive rather than annoying: stacked-statement rejection, keywords
inside string literals, comment-split keywords, CTE names mistaken for tables,
forbidden tables reached through a subquery, writes whose FROM clause reads a
table the agent may not see, and masking applied through aliases.
scripts/demo.ts covers the protocol layer itself — that tools are registered
with the schemas MCP expects, that denials come back as tool errors rather than
crashes, and that declining an approval leaves the data untouched.
Deliberately not included
Query rewriting — injecting predicates for row-level security is attractive and fragile. Postgres RLS does it properly; use it.
Connection pooling per agent identity — belongs in the deployment, not here.
Automatic PII detection — heuristics that guess which columns are sensitive are wrong in both directions. Name them explicitly.
License
MIT
Available Tools
4 toolsdescribe_tableDescribe tableARead-onlyIdempotent
Columns, types, nullability and defaults for one table. Masked columns are marked — they can be filtered on, but their values are never returned.
| Name | Required | Description | Default |
|---|---|---|---|
| table | Yes | Table name, optionally schema-qualified (e.g. public.users) |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint and idempotentHint. The description adds that masked columns are marked and can be filtered but never returned, which is useful behavioral context beyond the schema.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two sentences with no wasted words. The first sentence states the core purpose, the second adds an important detail about masked columns.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
The description is complete for a simple schema inspection tool: it explains what is returned (columns, types, nullability, defaults, masked marks) given one parameter. However, it could mention the return format (list of columns) explicitly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the schema already documents the parameter. The description does not add new information beyond what the schema provides (table name, optionally schema-qualified).
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool returns columns, types, nullability, and defaults for a single table, which matches the tool name and distinguishes it from siblings like list_tables (lists tables) and query (runs queries).
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description implies the tool is for inspecting table schema, but it does not explicitly state when to use it over siblings like list_tables or query, nor does it provide when-not-to-use guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
executeExecute a writeADestructive
Runs a single INSERT, UPDATE or DELETE. The change is first executed in a rolled-back transaction to measure exactly how many rows it affects, then that number is shown to a human for approval. UPDATE/DELETE without a WHERE clause is always refused.
| Name | Required | Description | Default |
|---|---|---|---|
| sql | Yes | A single INSERT, UPDATE or DELETE statement | |
| params | No | Values for $1, $2 … placeholders | |
| reason | Yes | Why this change is needed — shown to the approver |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The description adds significant behavioral context beyond annotations (readOnlyHint=false, destructiveHint=true): it explains the rolled-back transaction to measure row count, human approval step, and refusal of UPDATE/DELETE without WHERE. This fully discloses safety mechanisms.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two sentences, no wasted words. The first sentence states the primary purpose, the second elaborates on behavior. Front-loaded and efficient.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the complexity of a write tool with safety concerns, the description explains the dry-run and approval flow. No output schema exists, but the description mentions row count measurement. Not fully explicit about return format, but adequate for the intended usage.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so baseline is 3. The description does not add substantial meaning beyond the schema; it reinforces that 'reason' is shown to the approver, but this is already in the schema. No additional parameter guidance.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool runs a single INSERT, UPDATE, or DELETE statement, specifying the verb and resource. It distinguishes from siblings (list_tables, describe_table, query) by focusing on write operations.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description provides clear context for when to use this tool (single write statements) and notes that UPDATE/DELETE without a WHERE clause is refused. However, it does not explicitly name alternatives or state when not to use, though siblings imply read operations.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_tablesList tablesARead-onlyIdempotent
Lists tables the agent is permitted to read, with row estimates. Start here — guessing table names wastes calls against the rate limit.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already provide readOnlyHint and idempotentHint. Description adds value by stating it returns row estimates and mentioning rate limit context, beyond what annotations convey.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two concise sentences with no wasted words. Front-loaded with key action and followed by usage guidance. Every sentence earns its place.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given zero parameters and no output schema, description fully informs the agent: what tool returns, when to use, and why it matters. Sibling tools are distinct and covered.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
No parameters exist, so baseline is 4. Description implicitly tells agent no input needed, which adds clarity.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Description uses specific verb 'Lists' and resource 'tables' with additional detail 'the agent is permitted to read, with row estimates'. Clearly distinguishes from siblings like describe_table which describes a specific table.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly tells agent to 'Start here' and warns against guessing table names due to rate limits. Provides clear when-to-use guidance and alternatives (guessing is bad).
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
queryRun a read-only queryARead-only
Executes a single SELECT inside a read-only transaction, capped at 200 rows. Use $1, $2 … placeholders with the params array — never string-concatenate values into the SQL.
| Name | Required | Description | Default |
|---|---|---|---|
| sql | Yes | A single SELECT statement | |
| params | No | Values for $1, $2 … placeholders |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Description adds row cap and security guidance beyond the readOnlyHint annotation. No contradictions. The row limit is critical behavioral info not in annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two concise sentences, each essential. Front-loaded with key action and constraints. No wasted words.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a simple 2-param tool with no output schema, description covers purpose, constraints, and safe usage. Could mention return format, but implied by SQL execution.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema covers both parameters with descriptions. Description adds anti-pattern warning and clarifies parameter roles (placeholders for $1, $2). Adds meaningful value beyond schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Description clearly states it executes a single SELECT in a read-only transaction with a 200-row cap. Distinguishes from siblings like execute (writes) and list_tables/describe_table (metadata).
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Provides explicit guidance on using placeholders and warns against string concatenation. Does not explicitly mention when not to use, but read-only hint and sibling context imply appropriate use cases.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections. Dates show when Glama detected each change.
4 tool updates
v0.1.0- First observed
describe_table - First observed
execute - First observed
list_tables - First observed
query
TDQS
Each tool targets a distinct action: listing tables, describing schema, executing read queries, and performing safe mutations. There is no overlap in functionality.
All tool names follow a consistent snake_case verb_noun pattern: list_tables, describe_table, query, execute. The naming is predictable and clear.
With 4 tools, the server is well-scoped for its purpose of providing safe database access. Each tool plays a necessary role without redundancy.
Covers essential operations: schema exploration, reading, and safe writes. Minor gap for multi-statement transactions, but the guard design prioritizes safety.
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Connectors
Analytical memory for AI agents: a real Postgres queried in plain English over MCP. One command.
- XataOAuthio.github.xataio
Xata MCP server lets AI agents interact with your Xata projects, and Postgres database branches.
MCP server connecting AI agents to 100+ apps (Gmail, Slack, Notion, GitHub) via one-click OAuth.
Safe, read-only Postgres and MySQL access for AI agents. Audit log + column-level controls.
Related MCP Servers
- FlicenseNot gradedqualityDmaintenanceAn MCP server that gives an AI agent scoped, safe access to your Postgres databases with per-connection access control, row caps, timeouts, and defense-in-depth read-only enforcement.-
- AlicenseNot gradedqualityDmaintenanceA Model Context Protocol (MCP) server that provides secure, role-based access to PostgreSQL databases for AI agents.MIT
- AlicenseAqualityDmaintenanceA production-grade MCP server that gives AI agents safe, authenticated access to a PostgreSQL database.3MIT
- AlicenseAqualityAmaintenanceAn MCP server that gives AI agents access to configured databases (PostgreSQL, MySQL, Redshift, SQL Server) with SSH/AWS SSM tunnels, pluggable secret providers, and strict per-instance isolation.1079MIT
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/vladshykanov/mcp-postgres-guard'
If you have feedback or need assistance with the MCP directory API, please join our Discord server