{"_id":"postgres-mcp-safe","_rev":"5-30332df9fd211d285f878b444ef220cf","name":"postgres-mcp-safe","dist-tags":{"latest":"0.1.3"},"versions":{"0.0.0-stage":{"name":"postgres-mcp-safe","version":"0.0.0-stage","_id":"postgres-mcp-safe@0.0.0-stage","stub":true,"maintainers":[{"name":"sandman-js","email":"poudel0sandesh@gmail.com"}],"dist":{"shasum":"6b98406ce545ec39e90a7d482c57fac2c9c8858f","tarball":"https://registry.npmjs.org/postgres-mcp-safe/-/postgres-mcp-safe-0.0.0-stage.tgz","fileCount":2,"integrity":"sha512-e1ms0ArSULZZhb8Pnmk5iN8Jdl65TIKO9I5yvasZ/v1Y4bV/NygpbjYejoKmCufU94RCr+j+bcnhu5DkSW0ULQ==","signatures":[{"sig":"MEQCIDMN8Zw/voGAXHpP2f2138QgBJ/nGmsXIIWY/GS2iYReAiATMozCijbhIuT2Ey2eVxd6of2Fmqs5FlgD/Rf/Y2GxfQ==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":339},"_npmUser":{"name":"sandman-js","email":"poudel0sandesh@gmail.com"},"description":"Temporary package placeholder for staged publishing","directories":{}},"0.1.0":{"name":"postgres-mcp-safe","version":"0.1.0","keywords":["mcp","model-context-protocol","postgres","postgresql","read-only","server-postgres"],"license":"MIT","_id":"postgres-mcp-safe@0.1.0","maintainers":[{"name":"sandman-js","email":"poudel0sandesh@gmail.com"}],"homepage":"https://github.com/Poudel0/postgres-mcp-safe","bugs":{"url":"https://github.com/Poudel0/postgres-mcp-safe/issues"},"bin":{"postgres-mcp-safe":"bin/postgres-mcp-safe.js"},"dist":{"shasum":"9231e05c913e435c74347ffb7ef297f77a4a9d5c","tarball":"https://registry.npmjs.org/postgres-mcp-safe/-/postgres-mcp-safe-0.1.0.tgz","fileCount":4,"integrity":"sha512-NIWxnywbG97h5s/RabW6OjTxabO4URJ+r4CQV9/uaykXSkQh6UYJwC53Ez3xUYiUeOjgqKE/Otp/e9Q5+q/cmw==","signatures":[{"sig":"MEUCIQCypYngGQYxWfArW4uRE5lSonx3DR9BkDyjkbWaaVTWgQIgVrj6gg5SK0Re0NvPXlZunbLSZRpaDN8m6xWQ6XygLmU=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEUCIBOC2JPfRy/fttOXSMuVzsCutytR/LHd7cqW3VV+z/9MAiEA7XmH4YzFxxlw70gjTHY9KRMzAGV9/W4CvpreDlJYI74=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":9833},"engines":{"node":">=18"},"mcpName":"io.github.Poudel0/postgres-mcp-safe","_npmUser":{"name":"sandman-js","email":"poudel0sandesh@gmail.com"},"repository":{"url":"git+https://github.com/Poudel0/postgres-mcp-safe.git","type":"git"},"_npmVersion":"11.19.0","description":"Drop-in replacement for @modelcontextprotocol/server-postgres that is actually read-only.","directories":{},"_nodeVersion":"26.7.0","_hasShrinkwrap":false,"optionalDependencies":{"postgres-mcp-safe-linux-x64":"0.1.0","postgres-mcp-safe-win32-x64":"0.1.0","postgres-mcp-safe-darwin-x64":"0.1.0","postgres-mcp-safe-linux-arm64":"0.1.0","postgres-mcp-safe-darwin-arm64":"0.1.0"},"_npmOperationalInternal":{"tmp":"tmp/postgres-mcp-safe_0.1.0_1793516383165_0.1399745210096922","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"postgres-mcp-safe","version":"0.1.1","keywords":["mcp","model-context-protocol","postgres","postgresql","read-only","server-postgres"],"license":"MIT","_id":"postgres-mcp-safe@0.1.1","maintainers":[{"name":"sandman-js","email":"poudel0sandesh@gmail.com"}],"homepage":"https://github.com/Poudel0/postgres-mcp-safe","bugs":{"url":"https://github.com/Poudel0/postgres-mcp-safe/issues"},"bin":{"postgres-mcp-safe":"bin/postgres-mcp-safe.js"},"dist":{"shasum":"79860fe8c795eeb3d1e587cee15eac4a9427448e","tarball":"https://registry.npmjs.org/postgres-mcp-safe/-/postgres-mcp-safe-0.1.1.tgz","fileCount":4,"integrity":"sha512-mgrhfLcqv4o+67o20U4Pe/gcRNR3XSrW5gtsMZ7TbQcDK+rdcbrnM/EPZBiICSuKjOwqyR8FIoZ9tbVhoQ0Ipw==","signatures":[{"sig":"MEUCIANapApffmdRkQ+3zhNq53Ab+EY72qhX79tyOV05KrTeAiEA3n5JUSkLMj4iYEwHvnbKf9+Pn6VEGsw4FBN8sfSr9rU=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEYCIQCyMIvEYmpv32s5HFchxqFxOUa3zwkckJdX0PlkjCU6rQIhAJTKUUbqO2U3gjg0e4W/63RgZb5LkaR3tLVqByjNTi0o","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/postgres-mcp-safe@0.1.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":9833},"engines":{"node":">=18"},"gitHead":"eb634468a59a5d1a0300a24ac0b274c6c646f5ae","mcpName":"io.github.Poudel0/postgres-mcp-safe","_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"b8ad12fe-3ede-4013-ae0c-5470513495d1"}},"repository":{"url":"git+https://github.com/Poudel0/postgres-mcp-safe.git","type":"git"},"_npmVersion":"11.19.0","description":"Drop-in replacement for @modelcontextprotocol/server-postgres that is actually read-only.","directories":{},"_nodeVersion":"24.22.1","_hasShrinkwrap":false,"optionalDependencies":{"postgres-mcp-safe-linux-x64":"0.1.1","postgres-mcp-safe-win32-x64":"0.1.1","postgres-mcp-safe-darwin-x64":"0.1.1","postgres-mcp-safe-linux-arm64":"0.1.1","postgres-mcp-safe-darwin-arm64":"0.1.1"},"_npmOperationalInternal":{"tmp":"tmp/postgres-mcp-safe_0.1.1_1793516654433_0.5716883362993035","host":"s3://npm-registry-packages-npm-production"}},"0.1.2":{"name":"postgres-mcp-safe","version":"0.1.2","keywords":["mcp","model-context-protocol","postgres","postgresql","read-only","server-postgres"],"license":"MIT","_id":"postgres-mcp-safe@0.1.2","maintainers":[{"name":"sandman-js","email":"poudel0sandesh@gmail.com"}],"homepage":"https://github.com/Poudel0/postgres-mcp-safe","bugs":{"url":"https://github.com/Poudel0/postgres-mcp-safe/issues"},"bin":{"postgres-mcp-safe":"bin/postgres-mcp-safe.js"},"dist":{"shasum":"2e8f7c836017337dcb9d2af4649ab73368d87246","tarball":"https://registry.npmjs.org/postgres-mcp-safe/-/postgres-mcp-safe-0.1.2.tgz","fileCount":4,"integrity":"sha512-E78Q03WM/PBBWZbHg6TC/ytMtP88BPCCV87S1f+A6CJNBK1ajt4w7uoN4xaQwAqUonV+K6aC5fydnGZRxCD1ww==","signatures":[{"sig":"MEYCIQCkOMVw+UHQO44NjOCqaWMMUFAd6l3Rd2NYJe5+PUCVgwIhAKWAQ8JEkxHKQWNk23C/CRWP6WK1B0jewpmalH/AMdyE","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEUCIDey4GObPiwz2Krv5e3b9+eMbdVjzY0EEUt2fMR0MB6IAiEA3dyvtHNvrwuloHrehcZu3VfoSnKyHeFdk7JwVg19hNM=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/postgres-mcp-safe@0.1.2","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":10208},"engines":{"node":">=18"},"gitHead":"6da87692ea9ebd27f718fa006d3441b100a2af6d","mcpName":"io.github.Poudel0/postgres-mcp-safe","_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"b8ad12fe-3ede-4013-ae0c-5470513495d1"}},"repository":{"url":"git+https://github.com/Poudel0/postgres-mcp-safe.git","type":"git"},"_npmVersion":"11.19.0","description":"Drop-in replacement for @modelcontextprotocol/server-postgres that is actually read-only.","directories":{},"_nodeVersion":"24.22.1","_hasShrinkwrap":false,"optionalDependencies":{"postgres-mcp-safe-linux-x64":"0.1.2","postgres-mcp-safe-win32-x64":"0.1.2","postgres-mcp-safe-darwin-x64":"0.1.2","postgres-mcp-safe-linux-arm64":"0.1.2","postgres-mcp-safe-darwin-arm64":"0.1.2"},"_npmOperationalInternal":{"tmp":"tmp/postgres-mcp-safe_0.1.2_1793517185695_0.5903365068453668","host":"s3://npm-registry-packages-npm-production"}},"0.1.3":{"_id":"postgres-mcp-safe@0.1.3","bin":{"postgres-mcp-safe":"bin/postgres-mcp-safe.js"},"bugs":{"url":"https://github.com/Poudel0/postgres-mcp-safe/issues"},"dist":{"shasum":"701232c2fbba471e78a88991a6b00b21c51e7864","tarball":"https://registry.npmjs.org/postgres-mcp-safe/-/postgres-mcp-safe-0.1.3.tgz","fileCount":4,"integrity":"sha512-9AriNhGDSpw9+Y37ABWLfSLOaxeHeFxusQgV59N0qKO+FLzwxXaAiIgK/3oiFCQ8cwhj4L97cA0C/7hcPthFWg==","signatures":[{"sig":"MEUCIFi43QP9Pf5vGCznFuXb1FNPQwTQKlLUy7pewxAaoJRgAiEAqKO6qzPNcocnhTWmTwAjdFWOfwUVml2VGmS9aPb7HCE=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIA+QPYdENNz8P2uQ7fzzNr9sD4Ahu0lRxK3AKmg0gRpLAiANWGAjqrEcXZRvuvNj+9A85BRF+G4uD3lbk6hSSgqSDQ=="}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/postgres-mcp-safe@0.1.3","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":10650},"name":"postgres-mcp-safe","engines":{"node":">=18"},"gitHead":"b7e7ce5e399dc942a4bfc58fbd9966e6ff3a1b35","license":"MIT","mcpName":"io.github.Poudel0/postgres-mcp-safe","version":"0.1.3","_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"b8ad12fe-3ede-4013-ae0c-5470513495d1"}},"homepage":"https://github.com/Poudel0/postgres-mcp-safe","keywords":["mcp","model-context-protocol","postgres","postgresql","read-only","server-postgres"],"repository":{"url":"git+https://github.com/Poudel0/postgres-mcp-safe.git","type":"git"},"_npmVersion":"11.19.0","description":"Drop-in replacement for @modelcontextprotocol/server-postgres that is actually read-only.","directories":{},"maintainers":[{"name":"sandman-js","email":"poudel0sandesh@gmail.com"}],"_nodeVersion":"24.22.1","_hasShrinkwrap":false,"optionalDependencies":{"postgres-mcp-safe-linux-x64":"0.1.3","postgres-mcp-safe-win32-x64":"0.1.3","postgres-mcp-safe-darwin-x64":"0.1.3","postgres-mcp-safe-linux-arm64":"0.1.3","postgres-mcp-safe-darwin-arm64":"0.1.3"},"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/postgres-mcp-safe_0.1.3_1793720000856_0.26051883606204596"}}},"time":{"created":"2026-11-01T06:58:47.525Z","modified":"2026-11-03T15:33:21.303Z","0.0.0-stage":"2026-11-01T06:58:47.525Z","0.1.0":"2026-11-01T06:59:43.242Z","0.1.1":"2026-11-01T07:04:14.508Z","0.1.2":"2026-11-01T07:13:05.780Z","0.1.3":"2026-11-03T15:33:20.946Z"},"bugs":{"url":"https://github.com/Poudel0/postgres-mcp-safe/issues"},"license":"MIT","homepage":"https://github.com/Poudel0/postgres-mcp-safe","keywords":["mcp","model-context-protocol","postgres","postgresql","read-only","server-postgres"],"repository":{"url":"git+https://github.com/Poudel0/postgres-mcp-safe.git","type":"git"},"description":"Drop-in replacement for @modelcontextprotocol/server-postgres that is actually read-only.","maintainers":[{"name":"sandman-js","email":"poudel0sandesh@gmail.com"}],"readme":"# postgres-mcp-safe\n\nA drop-in replacement for the deprecated `@modelcontextprotocol/server-postgres`, but actually read-only.\n\nLets AI tools (Claude, Cursor, Zed, ...) query a Postgres database, read its schema and explain queries. Swap the\npackage name in your MCP config and keep everything else.\n\n[![npm](https://img.shields.io/npm/v/postgres-mcp-safe)](https://www.npmjs.com/package/postgres-mcp-safe) [![ci](https://github.com/Poudel0/postgres-mcp-safe/actions/workflows/ci.yml/badge.svg)](https://github.com/Poudel0/postgres-mcp-safe/actions/workflows/ci.yml)\n\n[![Postgres (read-only transactions) MCP server](https://mcprush.com/badge/postgres-read-only-safe-mcp/card.svg)](https://mcprush.com/to/postgres-read-only-safe-mcp?ref=badge)\n\n## Migrating from server-postgres\n\n```diff\n {\n   \"mcpServers\": {\n     \"postgres\": {\n       \"command\": \"npx\",\n-      \"args\": [\"-y\", \"@modelcontextprotocol/server-postgres\", \"postgresql://localhost/mydb\"]\n+      \"args\": [\"-y\", \"postgres-mcp-safe\", \"postgresql://localhost/mydb\"]\n     }\n   }\n }\n```\n\nDocker: `docker run -i --rm ghcr.io/poudel0/postgres-mcp-safe postgresql://host.docker.internal:5432/mydb`.\nBinaries for Linux, macOS and Windows are on the [releases page](https://github.com/Poudel0/postgres-mcp-safe/releases).\n\n**One catch:** if your URL logs in as a superuser (like Docker's default `postgres` user), every call is refused\nuntil you either [create a read-only role](#use-a-read-only-role-too) (recommended) or add `--allow-superuser`\nbefore the URL. A superuser can get around a read-only transaction, so we won't pretend otherwise.\n\nThe old server's `query` tool and `postgres://.../<table>/schema` resources work the same. See\n[Differences](#differences-from-server-postgres) for the small, deliberate changes.\n\n## Why\n\nThe old server's \"read-only\" mode can be broken with `COMMIT; DROP TABLE ...`\n([Datadog Security Labs, 2025-08-21](https://securitylabs.datadoghq.com/articles/mcp-vulnerability-case-study-SQL-injection-in-the-postgresql-mcp-server/)).\nA fix was merged upstream but never published, and the package is deprecated with no official successor.\n\n## Flags\n\nFlags go **before** the URL.\n\n| Flag | Default | |\n|---|---|---|\n| `--timeout` | `30s` | Time limit per call. |\n| `--max-rows` | `1000` | Rows returned per call. Extra rows are cut off with a note. |\n| `--max-bytes` | `262144` | JSON bytes returned per call, and the max size of a single row. |\n| `--allow-explain-analyze` | off | Lets `explain` run `EXPLAIN ANALYZE`, which executes the query. |\n| `--allow-superuser` | off | Allows a superuser role, or a member of one. Read-only is then **not** guaranteed. |\n\nEach call opens a fresh connection (that's part of the safety model). Against a remote database over TLS that costs a\nfew round trips: about 0.5 s per call at 50 ms latency. On Postgres 17+ you can save one round trip by adding\n`sslnegotiation=direct` to the URL.\n\nLimits can't be turned off; `0` is an error. The URL can also be a keyword DSN (`host=db dbname=app`), and the\nstandard `PG*` environment variables (`PGPASSWORD`, ...) are read as libpq would.\n\n## Tools\n\n| Tool | Input | |\n|---|---|---|\n| `query` | `sql` | One read-only statement. Returns a JSON array of rows. |\n| `list_tables` | `schema?` | Tables and views with schema and type. |\n| `describe_table` | `table`, `schema?` (`public`) | Columns, constraints and indexes. |\n| `explain` | `sql`, `analyze?` | Query plan. `analyze` needs `--allow-explain-analyze`. |\n\n## How read-only is enforced\n\nPostgres enforces it, not a SQL parser. Every call:\n\n1. Opens a **fresh connection** (no session state survives a call) and starts a `READ ONLY` transaction.\n2. Sends your SQL as a single protocol-level *Parse* message. Postgres rejects more than one statement there, so\n   `COMMIT; ...` fails before anything runs.\n3. Runs it only if Postgres says it returns rows. `COMMIT`, `SET`, `DO`, `COPY`, `CALL`, `PREPARE` and friends are\n   rejected without running.\n4. Applies `statement_timeout`, a client deadline, and row and byte caps.\n5. Closes the connection, which ends the transaction.\n\nThe [bypass suite](bypass_test.go) runs every known payload against a real database and checks a canary table after\neach one:\n\n| Payload | Result |\n|---|---|\n| `COMMIT; DROP SCHEMA public CASCADE;` | Rejected: multiple statements |\n| `COMMIT` / `SET TRANSACTION READ WRITE` / `DO $$...$$` / `CALL p()` | Rejected: returns no rows |\n| `COPY (SELECT 1) TO PROGRAM '...'` (as superuser) | Rejected: returns no rows |\n| `DELETE ... RETURNING *` / `WITH d AS (DELETE ...) SELECT ...` | Read-only transaction error |\n| `SELECT nextval('s')` | Read-only transaction error |\n| `SELECT set_config('transaction_read_only','off',true)` | Error |\n| `SELECT pg_sleep(600)`, even after `set_config('statement_timeout','0')` | Cancelled at `--timeout` |\n| `SELECT pg_advisory_lock(1)` | Released when the call ends |\n| `SELECT generate_series(1,10000000)` / `repeat('x', 10000000)` | Capped / rejected, memory stays bounded |\n| `explain` with `ANALYZE SELECT ...` | Syntax error: can't smuggle `ANALYZE` |\n\nFound a bypass? Open an issue. It becomes a test row before the fix.\n\n## Use a read-only role too\n\nThis server makes each call read-only **at the transaction level**. Some things are out of a transaction's reach,\nand only the database role stops them:\n\n- `dblink_exec(...)` opens a *separate* session that isn't read-only.\n- `pg_terminate_backend(...)` kills other connections.\n- A superuser can read server files (`pg_read_file`, `pg_ls_dir`): read-only stops writes, not reads.\n- A superuser can **write** server files with `lo_export`, even inside a read-only transaction.\n\nSo connect as a role that can only read:\n\n```sql\nCREATE ROLE mcp_ro LOGIN PASSWORD '...';\nGRANT pg_read_all_data TO mcp_ro;                     -- PG 14+; or GRANT SELECT on what it needs\nALTER ROLE mcp_ro SET default_transaction_read_only = on;\n```\n\nBecause of the last two, the server refuses superuser roles (and members of them) unless you pass `--allow-superuser`.\n\n## Differences from server-postgres\n\n1. Errors in a tool call come back as a tool result with `isError: true` and the Postgres message and SQLSTATE, so\n   the model can fix its SQL. The old server sent a JSON-RPC error.\n2. `resources/read` only shows columns of the `public` table, in column order. The old one mixed in columns from\n   same-named tables in other schemas.\n3. Percent-encoded table names in resource URIs are decoded (the old server never matched them).\n4. Values are what Postgres prints, typed where it's clear: booleans, numbers (including `bigint` and `numeric`)\n   and `json` are JSON values; everything else is a string such as `\"2026-01-02 03:04:05+00\"`, `\"\\\\x0102\"` or\n   `\"{x,y}\"`. `NaN` is `\"NaN\"` (the old server gave `null`).\n5. Statements that return no rows (`SET`, `DO`, `COPY`, ...) are rejected. The old server ran them and returned `[]`.\n\n## Development\n\n```sh\ndocker run -d --name pg-mcp-test -e POSTGRES_PASSWORD=postgres -p 127.0.0.1:5434:5432 postgres:17-alpine\nexport DATABASE_URL='postgresql://postgres:postgres@127.0.0.1:5434/postgres?sslmode=disable'\nmake test     # all tests, including the bypass suite\nmake golden   # re-record the old server's output (needs npx)\n```\n\n`DATABASE_URL` must be a superuser: the fixture resets the `public` schema and creates the `mcp_ro` role. Use a\nthrowaway database.\n","readmeFilename":"README.md"}