{"_id":"mcp-postgres-server","_rev":"6-7b6a90f0b0d7b3a5e9bdfb2a9a9e2c09","name":"mcp-postgres-server","dist-tags":{"latest":"0.3.1"},"versions":{"0.1.0":{"name":"mcp-postgres-server","version":"0.1.0","keywords":["mcp","model-context-protocol","postgres","postgresql","database","claude","anthropic"],"author":"","license":"MIT","_id":"mcp-postgres-server@0.1.0","maintainers":[{"name":"anton_ov","email":"4eladi@gmail.com"}],"bin":{"mcp-postgres":"build/index.js"},"dist":{"shasum":"c8f8b4bea53d4bd5e8e7b7857f239e7903f2d41b","tarball":"https://registry.npmjs.org/mcp-postgres-server/-/mcp-postgres-server-0.1.0.tgz","fileCount":3,"integrity":"sha512-n3tuYTBOFhHCkEykSlYFyupTifTbQ/IGhZOHcw4ZH+Xot/iMf2vMHNr23jOxdxU7IU4x/iXHXG/wlEuWzT4GfA==","signatures":[{"sig":"MEYCIQDWYOLCSzxcsJQSgSbdHAjkkuZmtE12Y3YdMaTwuHOgSQIhAO5fWefqXR9D06irLSXPIq41mogtW4+TGFfkkTdXm2lx","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":18106},"type":"module","gitHead":"1784ddf2e7a2ac1e24c1c6748fb4881d4685ffb5","scripts":{"build":"tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"","watch":"tsc --watch","prepare":"npm run build","inspector":"npx @modelcontextprotocol/inspector build/index.js"},"_npmUser":{"name":"anton_ov","email":"4eladi@gmail.com"},"repository":{"url":"","type":"git"},"_npmVersion":"10.4.0","description":"A Model Context Protocol server for PostgreSQL database operations","directories":{},"_nodeVersion":"20.11.1","dependencies":{"pg":"^8.11.3","dotenv":"^16.4.7","@modelcontextprotocol/sdk":"0.6.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"@types/pg":"^8.10.7","typescript":"^5.3.3","@types/node":"^20.11.24"},"_npmOperationalInternal":{"tmp":"tmp/mcp-postgres-server_0.1.0_1742826834379_0.5539789958197034","host":"s3://npm-registry-packages-npm-production"}},"0.1.2":{"name":"mcp-postgres-server","version":"0.1.2","keywords":["mcp","model-context-protocol","postgres","postgresql","database","claude","anthropic"],"author":"","license":"MIT","_id":"mcp-postgres-server@0.1.2","maintainers":[{"name":"anton_ov","email":"4eladi@gmail.com"}],"homepage":"https://github.com/antonorlov/mcp-postgres-server#readme","bugs":{"url":"https://github.com/antonorlov/mcp-postgres-server/issues"},"bin":{"mcp-postgres":"build/index.js"},"dist":{"shasum":"dae59f4389c543602cd1fda81abb7281028b44a3","tarball":"https://registry.npmjs.org/mcp-postgres-server/-/mcp-postgres-server-0.1.2.tgz","fileCount":3,"integrity":"sha512-3RKlahztw1nz6UuzCWlK9FGwOJVaadp+sPVsmEbqLDtKYRa1O3b+Zene6Kkmh31fi+UiIoITUmyYF4rux+yeHA==","signatures":[{"sig":"MEQCIC34fJEXnH0VLrlp+kMQMnaoeWQ3oezh/zPmJpshytUdAiAF8URG/6HBetKpEPgkZVL/ncCvi9pKPtALlJ+tYM6pUw==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":18182},"type":"module","gitHead":"2287de976ed4ed5f8336d2afe1bf6c75f9628838","scripts":{"build":"tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"","watch":"tsc --watch","prepare":"npm run build","inspector":"npx @modelcontextprotocol/inspector build/index.js"},"_npmUser":{"name":"anton_ov","email":"4eladi@gmail.com"},"repository":{"url":"git+https://github.com/antonorlov/mcp-postgres-server.git","type":"git"},"_npmVersion":"10.4.0","description":"A Model Context Protocol server for PostgreSQL database operations","directories":{},"_nodeVersion":"20.11.1","dependencies":{"pg":"^8.11.3","dotenv":"^16.4.7","@modelcontextprotocol/sdk":"0.6.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"@types/pg":"^8.10.7","typescript":"^5.3.3","@types/node":"^20.11.24"},"_npmOperationalInternal":{"tmp":"tmp/mcp-postgres-server_0.1.2_1742829364192_0.2125211076257345","host":"s3://npm-registry-packages-npm-production"}},"0.1.3":{"name":"mcp-postgres-server","version":"0.1.3","keywords":["mcp","model-context-protocol","postgres","postgresql","database","claude","anthropic"],"author":"","license":"MIT","_id":"mcp-postgres-server@0.1.3","maintainers":[{"name":"anton_ov","email":"4eladi@gmail.com"}],"homepage":"https://github.com/antonorlov/mcp-postgres-server#readme","bugs":{"url":"https://github.com/antonorlov/mcp-postgres-server/issues"},"bin":{"mcp-postgres":"build/index.js"},"dist":{"shasum":"707cdfa4a16957a242d4fda6e81674b41e0b9bf5","tarball":"https://registry.npmjs.org/mcp-postgres-server/-/mcp-postgres-server-0.1.3.tgz","fileCount":4,"integrity":"sha512-ZZKK16ienLRnxV5YV0k/G5AFdcvbokB8fodvzZp7FuZUQ+hdbMPlpxbbA54gCBTJK/ouEc8oyCAJqIHcYuVnTg==","signatures":[{"sig":"MEYCIQC5z8uTP/FdVKpkOSMg+ItqHZSHYtSDm/Ry1yEVcWInKgIhAN/52wis/qQ5G8lQZgEu0lcgG+2LRGdI00KeXWynwUf6","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":21705},"type":"module","gitHead":"17ef11f9867cd850fe5914dc62584208a0560019","scripts":{"build":"tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"","watch":"tsc --watch","prepare":"npm run build","inspector":"npx @modelcontextprotocol/inspector build/index.js"},"_npmUser":{"name":"anton_ov","email":"4eladi@gmail.com"},"repository":{"url":"git+https://github.com/antonorlov/mcp-postgres-server.git","type":"git"},"_npmVersion":"11.3.0","description":"A Model Context Protocol server for PostgreSQL database operations","directories":{},"_nodeVersion":"22.14.0","dependencies":{"pg":"^8.11.3","dotenv":"^16.4.7","@modelcontextprotocol/sdk":"0.6.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"@types/pg":"^8.10.7","typescript":"^5.3.3","@types/node":"^20.11.24"},"_npmOperationalInternal":{"tmp":"tmp/mcp-postgres-server_0.1.3_1744592991352_0.005755747942181255","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"mcp-postgres-server","version":"0.2.0","keywords":["mcp","model-context-protocol","postgres","postgresql","database","claude","anthropic"],"author":"","license":"MIT","_id":"mcp-postgres-server@0.2.0","maintainers":[{"name":"anton_ov","email":"4eladi@gmail.com"}],"homepage":"https://github.com/antonorlov/mcp-postgres-server#readme","bugs":{"url":"https://github.com/antonorlov/mcp-postgres-server/issues"},"bin":{"mcp-postgres":"build/index.js"},"dist":{"shasum":"6c979375f8995d09738132bb16622d3fb51cb646","tarball":"https://registry.npmjs.org/mcp-postgres-server/-/mcp-postgres-server-0.2.0.tgz","fileCount":5,"integrity":"sha512-UJKvVf5a+cOY7jDx4mo/tPRJSKJ1iLaqiprCxzolzgTOQCg5BmyOv1IbBnuKlqegtDb9RBMENknKRQ3RtCRbAg==","signatures":[{"sig":"MEQCIBnRaO0bc9uizqTxaF5f3D21JtIysdqKaJ38aQ5mgEKRAiBmM7f5jvDWe5JMTX+GyxHcvuHzPdPSTh5EM2/SEFVaYw==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEUCIDyny37Vi1xDnWW2EbsbG/Wod23E64YQkZZU/sfyV9DZAiEAudjhN+XiI0bjQcZcf4jMGrNAMV+INeEWO5rBQioTPGY=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":62526},"type":"module","engines":{"node":">=20"},"gitHead":"7e23fd4bba89a4bf06b3f29ef656cb2647d27570","mcpName":"io.github.antonorlov/mcp-postgres-server","scripts":{"test":"vitest run","build":"tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"","watch":"tsc --watch","prepare":"npm run build","coverage":"vitest run --coverage","inspector":"npx @modelcontextprotocol/inspector build/index.js"},"_npmUser":{"name":"anton_ov","email":"4eladi@gmail.com"},"repository":{"url":"git+https://github.com/antonorlov/mcp-postgres-server.git","type":"git"},"_npmVersion":"12.0.1","description":"A Model Context Protocol server for PostgreSQL database operations","directories":{},"_nodeVersion":"24.18.0","dependencies":{"pg":"^8.23.0","zod":"^3.25.0","pg-connection-string":"^2.14.0","@modelcontextprotocol/sdk":"^1.29.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vitest":"^3.2.4","@types/pg":"^8.10.7","typescript":"^5.3.3","@types/node":"^20.19.0","@types/ssh2":"^1.15.6","@vitest/coverage-v8":"^3.2.4"},"optionalDependencies":{"ssh2":"^1.17.0"},"_npmOperationalInternal":{"tmp":"tmp/mcp-postgres-server_0.2.0_1789258220518_0.7716635589680458","host":"s3://npm-registry-packages-npm-production"}},"0.3.0":{"name":"mcp-postgres-server","version":"0.3.0","keywords":["mcp","model-context-protocol","postgres","postgresql","database","claude","anthropic"],"author":"","license":"MIT","_id":"mcp-postgres-server@0.3.0","maintainers":[{"name":"anton_ov","email":"4eladi@gmail.com"}],"homepage":"https://github.com/antonorlov/mcp-postgres-server#readme","bugs":{"url":"https://github.com/antonorlov/mcp-postgres-server/issues"},"bin":{"mcp-postgres":"build/index.js"},"dist":{"shasum":"e6fbafb4470251f6383c60a742d8d45186b16bf9","tarball":"https://registry.npmjs.org/mcp-postgres-server/-/mcp-postgres-server-0.3.0.tgz","fileCount":6,"integrity":"sha512-IbGIa0iALOQ4mGC0b+RmvLsdfdKTlnQ6xj/p32SIcvKSMtNcwxNKQDPICVhm6gbDx3OlRD+O7ig5QdYH4EAuJw==","signatures":[{"sig":"MEUCIQCb+XDOYhtuBMBObY8ovahmNponqEAfFKw1yuK9R473ngIgGFKki+CqWoXM8UIn5bb0Oenq/gTnaPgKBnFi4Rju7FE=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEYCIQDxknbBDsc5EK1uML/wzTGzcdGZxGVg3Lsf63lWPowhPQIhAOOeQFzmhoSB94Gs1nyKhOsFvfNAdMhK4AclKG/itzlF","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":71811},"type":"module","engines":{"node":">=20"},"gitHead":"93e93a695dcc1b02ac38bd8c9bedf49e7924a919","mcpName":"io.github.antonorlov/mcp-postgres-server","scripts":{"test":"vitest run","build":"tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"","watch":"tsc --watch","prepare":"npm run build","coverage":"vitest run --coverage","inspector":"npx @modelcontextprotocol/inspector build/index.js"},"_npmUser":{"name":"anton_ov","email":"4eladi@gmail.com"},"repository":{"url":"git+https://github.com/antonorlov/mcp-postgres-server.git","type":"git"},"_npmVersion":"12.0.1","description":"A Model Context Protocol server for PostgreSQL database operations","directories":{},"_nodeVersion":"24.18.0","dependencies":{"pg":"^8.23.0","zod":"^3.25.0","pg-connection-string":"^2.14.0","@modelcontextprotocol/sdk":"^1.29.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vitest":"^3.2.4","@types/pg":"^8.10.7","typescript":"^5.3.3","@types/node":"^20.19.0","@types/ssh2":"^1.15.6","@vitest/coverage-v8":"^3.2.4"},"optionalDependencies":{"ssh2":"^1.17.0"},"_npmOperationalInternal":{"tmp":"tmp/mcp-postgres-server_0.3.0_1789293935424_0.7780100852397349","host":"s3://npm-registry-packages-npm-production"}},"0.3.1":{"_id":"mcp-postgres-server@0.3.1","bin":{"mcp-postgres":"build/index.js"},"bugs":{"url":"https://github.com/antonorlov/mcp-postgres-server/issues"},"dist":{"shasum":"32eee748c56bfc15b44cdd594df346a16dfdfee2","tarball":"https://registry.npmjs.org/mcp-postgres-server/-/mcp-postgres-server-0.3.1.tgz","fileCount":6,"integrity":"sha512-0FFBZlA/xc8A/JIQVfMD+jFZovmk6amYovuF+YfBEwZRpooc7Q4pDi3QRSMCPWYdUGWnAEGBhKGKBUzMAyK+FQ==","signatures":[{"sig":"MEUCIQCPyEDuL0u9QbQnAmwIl8GQc89KwYn6liZaSeQXuvxungIgEbERnMjPIWIlP9sLMdWJ/3N/9IXfnPqkgUHv9gTdV+Q=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIFBB5ufPk/r7T7ll7+raOMB552DXCJjdJdxLHxfrAFA5AiEArNPYasQzpUm7PilE/er46jcllPxRS3nIbSKd7R+ToxY="}],"unpackedSize":73058},"name":"mcp-postgres-server","type":"module","author":{"name":"antonorlov"},"engines":{"node":">=20"},"gitHead":"0ca233e5f4952d4cef71909052394479210a4c51","license":"MIT","mcpName":"io.github.antonorlov/mcp-postgres-server","scripts":{"test":"vitest run","build":"tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"","watch":"tsc --watch","prepare":"npm run build","coverage":"vitest run --coverage","inspector":"npx @modelcontextprotocol/inspector build/index.js"},"version":"0.3.1","_npmUser":{"name":"anton_ov","email":"4eladi@gmail.com"},"homepage":"https://github.com/antonorlov/mcp-postgres-server#readme","keywords":["mcp","mcp-server","model-context-protocol","postgres","postgresql","database","sql","ssh-tunnel","ai-agents","claude-code","llm","claude","cursor","anthropic"],"repository":{"url":"git+https://github.com/antonorlov/mcp-postgres-server.git","type":"git"},"_npmVersion":"12.0.1","description":"MCP server for PostgreSQL: local, Docker, RDS, Neon, Supabase, or behind an SSH bastion.","directories":{},"maintainers":[{"name":"anton_ov","email":"4eladi@gmail.com"}],"_nodeVersion":"24.18.0","dependencies":{"pg":"^8.23.0","zod":"^3.25.0","pg-connection-string":"^2.14.0","@modelcontextprotocol/sdk":"^1.29.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vitest":"^3.2.4","@types/pg":"^8.10.7","typescript":"^5.3.3","@types/node":"^20.19.0","@types/ssh2":"^1.15.6","@vitest/coverage-v8":"^3.2.4"},"optionalDependencies":{"ssh2":"^1.17.0"},"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/mcp-postgres-server_0.3.1_1789417350677_0.1664649891850425"}}},"time":{"created":"2025-03-24T14:33:54.287Z","modified":"2026-09-14T20:22:30.966Z","0.1.0":"2025-03-24T14:33:54.525Z","0.1.2":"2025-03-24T15:16:04.382Z","0.1.3":"2025-04-14T01:09:51.531Z","0.2.0":"2026-09-13T00:10:20.619Z","0.3.0":"2026-09-13T10:05:35.506Z","0.3.1":"2026-09-14T20:22:30.783Z"},"bugs":{"url":"https://github.com/antonorlov/mcp-postgres-server/issues"},"license":"MIT","homepage":"https://github.com/antonorlov/mcp-postgres-server#readme","keywords":["mcp","mcp-server","model-context-protocol","postgres","postgresql","database","sql","ssh-tunnel","ai-agents","claude-code","llm","claude","cursor","anthropic"],"repository":{"url":"git+https://github.com/antonorlov/mcp-postgres-server.git","type":"git"},"description":"MCP server for PostgreSQL: local, Docker, RDS, Neon, Supabase, or behind an SSH bastion.","maintainers":[{"name":"anton_ov","email":"4eladi@gmail.com"}],"readme":"# MCP PostgreSQL Server\n\n[![npm version](https://img.shields.io/npm/v/mcp-postgres-server.svg)](https://www.npmjs.com/package/mcp-postgres-server)\n[![CI](https://github.com/antonorlov/mcp-postgres-server/actions/workflows/ci.yml/badge.svg)](https://github.com/antonorlov/mcp-postgres-server/actions/workflows/ci.yml)\n\nA Model Context Protocol (MCP) server for PostgreSQL: local, Docker, RDS, Neon,\nand Supabase databases.\n\nThe server is small and auditable, with four runtime dependencies: the MCP SDK,\n`pg`, `pg-connection-string`, and `zod` (plus `ssh2`, an optional dependency used\nonly for SSH tunneling).\n\nRequires Node.js 20 or newer.\n\n## Quick start\n\nThe preferred way to configure the server is a single `DATABASE_URL`:\n\n```json\n{\n  \"mcpServers\": {\n    \"postgres\": {\n      \"type\": \"stdio\",\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"mcp-postgres-server\"],\n      \"env\": {\n        \"DATABASE_URL\": \"postgres://user:password@localhost:5432/mydb\",\n        \"PG_ALLOW_WRITE\": \"false\"\n      }\n    }\n  }\n}\n```\n\nWith `PG_ALLOW_WRITE` set to `\"false\"` the server has **read-only access** to the\ndatabase. This is the default; set it to `\"true\"` only if the model must write.\n\nThe same JSON works in any MCP client that speaks stdio: VS Code, Cursor, Claude Code, Codex, Windsurf.\n\nAlternatively, set the individual `PG_*` variables; they are used when\n`DATABASE_URL` is not set:\n\n```json\n{\n  \"mcpServers\": {\n    \"postgres\": {\n      \"type\": \"stdio\",\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"mcp-postgres-server\"],\n      \"env\": {\n        \"PG_HOST\": \"your_host\",\n        \"PG_PORT\": \"5432\",\n        \"PG_USER\": \"your_user\",\n        \"PG_PASSWORD\": \"your_password\",\n        \"PG_DATABASE\": \"your_database\",\n        \"PG_ALLOW_WRITE\": \"false\"\n      }\n    }\n  }\n}\n```\n\n### Manual Installation\n\n```bash\nnpm install mcp-postgres-server\n```\n\nOr run directly with:\n\n```bash\nnpx mcp-postgres-server\n```\n\n## Connect to your database\n\n**Local Postgres:**\n\n```\nDATABASE_URL=postgres://mcp_readonly:secret@localhost:5432/mydb\n```\n\n**Postgres in Docker:** if the database runs in a container with a published\nport, connect to `localhost:<published-port>` as usual. If the *MCP server\nitself* runs inside a container and the database runs on your host machine,\nuse `host.docker.internal` instead of `localhost`:\n\n```\nDATABASE_URL=postgres://mcp_readonly:secret@host.docker.internal:5432/mydb\n```\n\n**Amazon RDS:**\n\n```\nDATABASE_URL=postgres://mcp_readonly:secret@mydb.xxxxxx.us-east-1.rds.amazonaws.com:5432/mydb?sslmode=require\n```\n\n**Neon:**\n\n```\nDATABASE_URL=postgres://mcp_readonly:secret@ep-xxx-xxx.us-east-2.aws.neon.tech/mydb?sslmode=require\n```\n\n**Supabase:**\n\n```\nDATABASE_URL=postgres://postgres.xxxxxxxx:secret@aws-0-us-east-1.pooler.supabase.com:5432/postgres?sslmode=require\n```\n\n## Tools\n\nTool availability depends on configuration:\n\n| Tool | Available |\n|------|-----------|\n| `query`, `list_schemas`, `list_tables`, `describe_table` | Always |\n| `execute` | Always (refuses writes unless `PG_ALLOW_WRITE=true`) |\n| `connect_db` | Only when `PG_ENABLE_RUNTIME_CONNECT=true` |\n\n### 1. query\n\nExecute a read-only SQL statement. Accepts `SELECT`, `WITH ... SELECT`,\n`EXPLAIN`, and `SHOW`. One statement per call - multi-statement input is rejected\nby the extended query protocol. In read-only mode (the default) the statement runs\nas `BEGIN READ ONLY`, the query, and `ROLLBACK` - three commands, roughly two\nnetwork round trips with pipelining - so the database itself refuses any write.\nWith `PG_ALLOW_WRITE=true` the statement is sent directly, without that wrapper, so a\nwrite run through `query` would execute - use `execute` for writes.\nSupports PostgreSQL-style `$1, $2` prepared-statement parameters; values are bound\nby the driver and never spliced into the SQL text.\n\n```javascript\nuse_mcp_tool({\n  server_name: \"postgres\",\n  tool_name: \"query\",\n  arguments: {\n    sql: \"SELECT * FROM users WHERE id = $1\",\n    params: [1]\n  }\n});\n```\n\nReturns compact JSON: `{\"rows\": [...], \"rowCount\": n, \"returnedRows\": n, \"truncated\": false}`.\nWhen the serialized rows exceed `PG_MAX_RESULT_BYTES`, only the rows that fit are returned\n(`returnedRows < rowCount`), `truncated` is `true`, and a hint suggests adding `LIMIT`/`WHERE`\nor selecting fewer columns.\n\n### 2. list_schemas\n\nList all schemas in the connected database.\n\n```javascript\nuse_mcp_tool({\n  server_name: \"postgres\",\n  tool_name: \"list_schemas\",\n  arguments: {}\n});\n```\n\n### 3. list_tables\n\nList tables in the connected database. Accepts an optional schema parameter\n(defaults to 'public').\n\n```javascript\n// List tables in the 'public' schema (default)\nuse_mcp_tool({\n  server_name: \"postgres\",\n  tool_name: \"list_tables\",\n  arguments: {}\n});\n\n// List tables in a specific schema\nuse_mcp_tool({\n  server_name: \"postgres\",\n  tool_name: \"list_tables\",\n  arguments: {\n    schema: \"my_schema\"\n  }\n});\n```\n\n### 4. describe_table\n\nGet the structure of a specific table (columns, types, nullability, defaults,\nprimary keys). Accepts an optional schema parameter (defaults to 'public').\n\n```javascript\nuse_mcp_tool({\n  server_name: \"postgres\",\n  tool_name: \"describe_table\",\n  arguments: {\n    table: \"users\",\n    schema: \"my_schema\"  // optional\n  }\n});\n```\n\n### 5. execute - requires `PG_ALLOW_WRITE=true`\n\nExecute an `INSERT`, `UPDATE`, `DELETE`, or DDL statement. Always registered, but\nin read-only mode (the default) it refuses with an error naming `PG_ALLOW_WRITE`\nand changes nothing - the statement never reaches the database. With\n`PG_ALLOW_WRITE=true` it runs: same `$1, $2` parameter handling as `query`, one\ncomplete statement per call, and the connecting role governs what it may do.\nReturns `{\"rowCount\": n, \"command\": \"INSERT\"}`.\n\n```javascript\nuse_mcp_tool({\n  server_name: \"postgres\",\n  tool_name: \"execute\",\n  arguments: {\n    sql: \"INSERT INTO users (name, email) VALUES ($1, $2)\",\n    params: [\"John Doe\", \"john@example.com\"]\n  }\n});\n```\n\n### 6. connect_db - requires `PG_ENABLE_RUNTIME_CONNECT=true`\n\nConnect to a different PostgreSQL database at runtime using provided\ncredentials. Not registered by default - prefer configuring credentials\nthrough the environment so they never pass through model-visible arguments.\nSession limits (`statement_timeout`, `idle_in_transaction_session_timeout`) are\nre-applied after every reconnect; read-only reads enforce read-only in their own\n`BEGIN READ ONLY` transaction.\n\n```javascript\nuse_mcp_tool({\n  server_name: \"postgres\",\n  tool_name: \"connect_db\",\n  arguments: {\n    host: \"localhost\",\n    port: 5432,\n    user: \"your_user\",\n    password: \"your_password\",\n    database: \"your_database\"\n  }\n});\n```\n\n## Configuration reference\n\n| Variable | Default | Description |\n|----------|---------|-------------|\n| `DATABASE_URL` | - | Full connection string (preferred). Supports `?sslmode=` in the URL. |\n| `PG_HOST` | - | Database host (fallback when `DATABASE_URL` is not set) |\n| `PG_PORT` | `5432` | Database port |\n| `PG_USER` | - | Database user |\n| `PG_PASSWORD` | - | Database password |\n| `PG_DATABASE` | - | Database name |\n| `PG_ALLOW_WRITE` | `false` | When `true`, `execute` performs writes and reads are sent directly. Off (default) is read-only: `execute` refuses writes and each read runs in a `READ ONLY` transaction |\n| `PG_SSLMODE` | - | `disable` \\| `allow` \\| `prefer` \\| `require` \\| `verify-ca` \\| `verify-full`. `require`/`allow`/`prefer` encrypt without verifying the certificate; `verify-ca`/`verify-full` verify (supply a CA via `PG_SSL_CA`). Unrecognized values fail at startup. **Limitation:** unlike libpq, `allow`/`prefer` do not fall back to plaintext (node-postgres has no opportunistic SSL), so a server without TLS needs `disable`. |\n| `PG_SSL_CA` | - | Path to a CA certificate file. Setting it by itself implies `verify-full` |\n| `PG_ENABLE_RUNTIME_CONNECT` | `false` | Register the `connect_db` tool (runtime credential switching) |\n| `PG_MAX_RESULT_BYTES` | `32768` | Byte budget for a `query` result sent to the model. Whole rows are kept while they fit; over the budget `returnedRows < rowCount` and `truncated: true` (if not even the first row fits, `returnedRows` is 0 with a hint). ~32 KiB ≈ 8k tokens; lower it for strict clients, raise it if your client allows more. |\n| `PG_STATEMENT_TIMEOUT` | `30000` | Statement timeout in milliseconds, applied to every session |\n| `PG_CONNECT_TIMEOUT` | `10000` | Timeout in milliseconds for a single connect attempt (raise it for slow links or SSH tunnels) |\n\nTo reach a database only accessible through a bastion, see [SSH tunneling](#ssh-tunneling) (adds `PG_SSH_*` variables).\n\n## Features\n\n* Read-only by default; writes are an explicit opt-in (`PG_ALLOW_WRITE=true`)\n* Read-only enforced by the engine (`BEGIN READ ONLY`), never by client-side SQL parsing\n* Data access behind a small typed interface; the `pg` driver never leaks past it\n* `DATABASE_URL` support with SSL (`sslmode=disable|allow|prefer|require|verify-ca|verify-full`, custom CA)\n* Prepared-statement parameters: `$1`-style placeholders, bound by the driver\n* Result size cap (byte budget) with an explicit `truncated` flag instead of flooding the model's context\n* Session statement timeout plus a client deadline; transaction poolers may not preserve session settings\n* Errors returned as readable tool results with SQLSTATE-based hints, so the model can self-correct\n* Survives dropped connections - reconnects lazily instead of crashing\n* Optional SSH tunneling (`PG_SSH_*`) with mandatory host-key verification, loaded only when configured\n* MCP tool annotations (read-only / destructive hints) per spec 2025-11-25\n* Multi-schema support for database operations\n\n## Security\n\nFull details, including the threat model and disclosure process, are in\n[SECURITY.md](SECURITY.md). The short version:\n\n1. **A least-privilege database role is the real boundary.** The MCP works with\n   existing credentials; creating or changing roles is not required. A dedicated\n   role is what actually guarantees writes are impossible.\n   On PostgreSQL 14+, the following is a starting point:\n\n   ```sql\n   CREATE ROLE mcp_readonly LOGIN PASSWORD 'change-me';\n   GRANT CONNECT ON DATABASE your_database TO mcp_readonly;\n   GRANT pg_read_all_data TO mcp_readonly;                          -- adds read privileges\n   ALTER ROLE mcp_readonly SET default_transaction_read_only = on;  -- read-only by default\n   ```\n\n   (On PostgreSQL 13 or older, grant `SELECT` explicitly instead of\n   `pg_read_all_data` - see [SECURITY.md](SECURITY.md).) The server warns on\n   stderr if you connect as a superuser. Read grants do not revoke existing\n   privileges, and defaults remain mutable; available functions, ownership and\n   inherited privileges also matter.\n\n2. **The engine enforces read-only.** There is no client-side SQL parsing. In\n   read-only mode every read runs in a rolled-back `BEGIN READ ONLY` transaction,\n   so PostgreSQL itself - which alone knows what a function, view, or rule does -\n   refuses any write with SQLSTATE 25006 and reverts any session change the\n   statement made. The extended protocol rejects multi-command strings.\n\n**Honest framing:** the read-only transaction is defense-in-depth on top of the\nrole, not a replacement for it. Read-only mode stops a confused or prompt-injected\nmodel from *writing* to your database; it does not stop prompt injection carried in\nthe row data a query returns. Don't point this server at production - use a replica,\na snapshot, or a tightly scoped role. See [SECURITY.md](SECURITY.md).\n\n## SSH tunneling\n\nSet `PG_SSH_HOST` (plus auth and host-key verification) to reach a database that is only accessible\nthrough a bastion (an SSH jump host). The connection string / `PG_*` fields then describe the\ndatabase **as seen from the bastion**:\n\n```json\n{\n  \"mcpServers\": {\n    \"postgres\": {\n      \"type\": \"stdio\",\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"mcp-postgres-server\"],\n      \"env\": {\n        \"DATABASE_URL\": \"postgres://mcp_readonly:secret@db.internal:5432/mydb?sslmode=verify-full\",\n        \"PG_SSH_HOST\": \"bastion.example.com\",\n        \"PG_SSH_USER\": \"jump\",\n        \"PG_SSH_PRIVATE_KEY\": \"/home/me/.ssh/id_ed25519\",\n        \"PG_SSH_FINGERPRINT\": \"SHA256:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\"\n      }\n    }\n  }\n}\n```\n\n| Variable | Default | Description |\n|----------|---------|-------------|\n| `PG_SSH_HOST` | - | SSH bastion host. **Setting it enables tunneling**: the server reaches the database only through an SSH tunnel to this host (see below). Optional feature; needs the `ssh2` optional dependency. |\n| `PG_SSH_PORT` | `22` | SSH bastion port |\n| `PG_SSH_USER` | - | SSH username |\n| `PG_SSH_PRIVATE_KEY` | - | Path to a private key file. If unset, auth falls back like `ssh`: a running agent (`SSH_AUTH_SOCK`), then a default key (`~/.ssh/id_ed25519`, `id_rsa`, `id_ecdsa`) |\n| `PG_SSH_PASSPHRASE` | - | Passphrase for the private key, if encrypted |\n| `PG_SSH_AGENT` | - | `true` to use the ambient agent (`SSH_AUTH_SOCK`), or an explicit socket path / Windows named pipe (`\\\\.\\pipe\\openssh-ssh-agent`) |\n| `PG_SSH_PASSWORD` | - | SSH login password. Opt-in; a key or agent takes precedence. Prefer keys - a bastion often disables password auth. |\n| `PG_SSH_FINGERPRINT` | - | Pinned host-key fingerprint (`SHA256:...`). **Host-key verification is mandatory and set only this way**: without it the tunnel refuses to connect (fail-closed). Get it with `ssh-keygen -lF host` (reads your `known_hosts`) or `ssh-keyscan host \\| ssh-keygen -lf -` (see the trust note below) |\n| `PG_SSH_KEEPALIVE_INTERVAL` | `15000` | SSH keepalive interval in ms; the tunnel drops after 3 unanswered keepalives, and the next call reconnects |\n\n- **SSH changes only the transport.** Read-only enforcement, the result size cap, timeouts, and\n  `connect_db` behave exactly as on a direct connection, and no extra SQL is sent per query.\n- **Host-key verification is mandatory** via a pinned `PG_SSH_FINGERPRINT` - the tunnel will not\n  connect without it, so a man-in-the-middle bastion is refused. Get the fingerprint over a channel\n  you trust, most trustworthy first:\n  - on the bastion itself, or from its admin: `ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub`\n    (no network involved);\n  - from your existing `~/.ssh/known_hosts`, if you already reach the host over `ssh`:\n    `ssh-keygen -lF bastion.example.com`;\n  - fetched from the host: `ssh-keyscan bastion.example.com | ssh-keygen -lf -` (trust this only\n    when run from a network position you trust - it accepts whatever the host returns).\n- **TLS validates the real database hostname.** With `verify-full`, the certificate is checked against\n  the database's own hostname (e.g. `db.internal`), not the loopback the tunnel binds locally, and\n  `rejectUnauthorized` is pinned on so an inherited `NODE_TLS_REJECT_UNAUTHORIZED=0` cannot disable it.\n- **`ssh2` is an optional dependency**, loaded only when `PG_SSH_HOST` is set, so a direct connection\n  never initializes it. npm installs optional dependencies by default; run\n  `npm install --omit=optional` to skip it entirely (a direct connection does not need it).\n\nA tunneled connection that fails reports a stable `SSH_*` code - see [Error Handling](#error-handling).\n\n## Error Handling\n\nSQL and connection failures are returned as tool results (`isError: true`)\nwith a message, the SQLSTATE code, and a hint. PostgreSQL's own server hint is\nused when present; otherwise these fallbacks apply:\n\n| code | meaning | first thing to check |\n|------|---------|----------------------|\n| `28P01` | authentication failed | `PG_USER` / `PG_PASSWORD` |\n| `3D000` | database does not exist | `PG_DATABASE` |\n| `42P01` | relation not found | call `list_tables` |\n| `42703` | column not found | call `describe_table` |\n| `57014` | the query was canceled (a timeout or a cancel request) | if timing out, add a `LIMIT` / simplify it, or raise `PG_STATEMENT_TIMEOUT` |\n| `25006` | the transaction is read-only | source may be a read-only role, a replica, a server default, or (for `query`) the read-only wrapper; `execute` writes need `PG_ALLOW_WRITE=true` |\n| `ECONNREFUSED` / `ENOTFOUND` | cannot reach or resolve the database host | `PG_HOST` / `PG_PORT` / `DATABASE_URL` |\n\nOver an [SSH tunnel](#ssh-tunneling), a failure carries a stable `code` (and, where\nthe cause is determinate, a hint naming the setting to fix), so the failing phase is unambiguous:\n\n| code | meaning | first thing to check |\n|------|---------|----------------------|\n| `SSH_CONFIG_INVALID` | invalid SSH config, incl. a malformed `PG_SSH_FINGERPRINT` | the `PG_SSH_*` values |\n| `SSH_KEY_INVALID` | key unreadable, unparseable, a public key, or encrypted without the right passphrase (an encrypted key with the correct `PG_SSH_PASSPHRASE` works) | `PG_SSH_PRIVATE_KEY`, `PG_SSH_PASSPHRASE` |\n| `SSH_CONNECT_FAILED` | the bastion is unreachable, or SSH setup failed for an unclassified reason | `PG_SSH_HOST`, `PG_SSH_PORT`, reachability |\n| `SSH_TIMEOUT` | the bastion did not respond in time | network/firewall, `PG_CONNECT_TIMEOUT` |\n| `SSH_AUTH_FAILED` | the bastion rejected authentication | `PG_SSH_USER` and the key/agent/password in use |\n| `SSH_HOST_KEY_MISMATCH` | host key does not match `PG_SSH_FINGERPRINT` (stale value or MITM) | re-fetch the fingerprint |\n| `SSH_FORWARD_FAILED` | tunnel is up, but the bastion could not reach the database | the DB host and port as seen from the bastion |\n| `SSH_CONNECTION_LOST` | an established tunnel dropped mid-session | transient; the next call reconnects |\n\nA genuine PostgreSQL error through a healthy tunnel keeps its own code (e.g. `28P01` for wrong\ndatabase credentials), not an SSH code.\n\n## Migrating from 0.1.x\n\nNot needed for new installs. Two behavior changes since 0.1.x:\n\n1. **Read-only by default.** The `execute` tool is always visible but refuses\n   writes (with an error naming the flag) unless `PG_ALLOW_WRITE=true`, and every\n   read runs inside an engine-enforced `READ ONLY` transaction. If your workflow\n   writes to the database, set `\"PG_ALLOW_WRITE\": \"true\"` to restore 0.1.x behavior.\n2. **`connect_db` is disabled by default.** Runtime connection switching (passing\n   credentials through tool arguments) requires `PG_ENABLE_RUNTIME_CONNECT=true`;\n   otherwise connection details come only from the environment.\n\nTool names, parameter names, and `PG_*` variables are unchanged. Result payloads\nare now structured compact JSON for **every** tool (e.g. `query` returns\n`{rows, rowCount, returnedRows, truncated}` instead of a bare row array) - see\n[CHANGELOG.md](CHANGELOG.md) for the exact shapes before updating anything that\nparses tool output.\n\n## License\n\nMIT\n","readmeFilename":"README.md","author":{"name":"antonorlov"}}