@edelciomolina/postgres-mcp 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +138 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +144 -0
- package/package.json +43 -0
package/README.md
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# 🐘 Postgres MCP
|
|
2
|
+
|
|
3
|
+
> 🔌 MCP server wrapper for PostgreSQL - reads credentials from `.env` at runtime with flexible key mapping, configurable tool selection, and **safe read-only defaults**.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@edelciomolina/postgres-mcp)
|
|
6
|
+
[](./LICENSE)
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## ✨ What it does
|
|
11
|
+
|
|
12
|
+
This package wraps [@henkey/postgres-mcp-server](https://github.com/HenkDz/postgresql-mcp-server) and adds:
|
|
13
|
+
|
|
14
|
+
- 🔐 **Runtime credential resolution** - reads database credentials from your `.env` file at startup, so no secrets are stored in `mcp.json`
|
|
15
|
+
- 🗝️ **Flexible key mapping** - use any `.env` variable names; tell the server which ones to use via `env` in `mcp.json`
|
|
16
|
+
- 🎯 **Explicit tool selection** - pass `tool=<name>` args to choose exactly which MCP tools to expose
|
|
17
|
+
- 🛡️ **Read-only by default** - if no tools are specified, only safe introspection tools are enabled (no writes, no arbitrary SQL execution)
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## 📋 Requirements
|
|
22
|
+
|
|
23
|
+
- ⚙️ Node.js >= 18
|
|
24
|
+
- 📄 A `.env` file in your project root with the database credentials
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## 🚀 Usage in VS Code (`mcp.json`)
|
|
29
|
+
|
|
30
|
+
```json
|
|
31
|
+
{
|
|
32
|
+
"servers": {
|
|
33
|
+
"Postgres": {
|
|
34
|
+
"type": "stdio",
|
|
35
|
+
"command": "npx",
|
|
36
|
+
"args": [
|
|
37
|
+
"@edelciomolina/postgres-mcp",
|
|
38
|
+
"tool=pg_explain_query",
|
|
39
|
+
"tool=pg_get_schema_info",
|
|
40
|
+
"tool=pg_get_indexes",
|
|
41
|
+
"tool=pg_get_slow_queries",
|
|
42
|
+
"tool=pg_monitor_database"
|
|
43
|
+
],
|
|
44
|
+
"env": {
|
|
45
|
+
"MCP_KEY_HOST": "DB_HOST",
|
|
46
|
+
"MCP_KEY_PORT": "DB_PORT",
|
|
47
|
+
"MCP_KEY_NAME": "DB_NAME",
|
|
48
|
+
"MCP_KEY_SSLMODE": "DB_SSLMODE",
|
|
49
|
+
"MCP_KEY_USER": "DB_USER",
|
|
50
|
+
"MCP_KEY_PASS": "DB_PASS"
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
The corresponding `.env` in your project root:
|
|
58
|
+
|
|
59
|
+
```env
|
|
60
|
+
DB_HOST=db.your-project.supabase.co
|
|
61
|
+
DB_PORT=5432
|
|
62
|
+
DB_NAME=postgres
|
|
63
|
+
DB_SSLMODE=require
|
|
64
|
+
DB_USER=readonly_user
|
|
65
|
+
DB_PASS=your_password
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## ⚙️ How `mcp.json` configuration works
|
|
71
|
+
|
|
72
|
+
### 🗝️ `env` - credential key mapping
|
|
73
|
+
|
|
74
|
+
The `env` block does **not** contain the actual credentials. It maps each `MCP_KEY_*` to the name of the variable in your `.env` file.
|
|
75
|
+
|
|
76
|
+
| Key in `env` | Points to `.env` variable | Example value |
|
|
77
|
+
|----------------|---------------------------|-------------------------|
|
|
78
|
+
| `MCP_KEY_HOST` | `DB_HOST` | `db.example.supabase.co`|
|
|
79
|
+
| `MCP_KEY_PORT` | `DB_PORT` | `5432` |
|
|
80
|
+
| `MCP_KEY_NAME` | `DB_NAME` | `postgres` |
|
|
81
|
+
| `MCP_KEY_SSLMODE` | `DB_SSLMODE` | `require` |
|
|
82
|
+
| `MCP_KEY_USER` | `DB_USER` | `readonly_user` |
|
|
83
|
+
| `MCP_KEY_PASS` | `DB_PASS` | `secret` |
|
|
84
|
+
|
|
85
|
+
This indirection means you can use **any variable names** in your `.env` - useful when sharing an `.env` across multiple services with different naming conventions.
|
|
86
|
+
|
|
87
|
+
### 🔧 `args` - tool selection via `tool=` prefix
|
|
88
|
+
|
|
89
|
+
Each enabled MCP tool is declared as a separate arg using the `tool=<name>` format:
|
|
90
|
+
|
|
91
|
+
```json
|
|
92
|
+
"args": [
|
|
93
|
+
"npx @edelciomolina/postgres-mcp",
|
|
94
|
+
"tool=pg_get_schema_info",
|
|
95
|
+
"tool=pg_get_indexes"
|
|
96
|
+
]
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
This makes the tool list **explicit and auditable** directly in `mcp.json` - no hidden config files. 🔍
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## 🛡️ Why read-only is the default
|
|
104
|
+
|
|
105
|
+
If you omit all `tool=` args, the server starts with a **curated read-only set** - every tool that can retrieve, analyze, or explain data, but nothing that can modify it.
|
|
106
|
+
|
|
107
|
+
**⚠️ Excluded from defaults (write-capable):**
|
|
108
|
+
|
|
109
|
+
| Tool | Risk |
|
|
110
|
+
|------|------|
|
|
111
|
+
| `pg_execute_query` | Runs arbitrary SQL - including `INSERT`, `UPDATE`, `DELETE`, `DROP` |
|
|
112
|
+
| `pg_manage_query` | Executes saved queries - can include mutations |
|
|
113
|
+
|
|
114
|
+
**✅ Included in defaults (safe read-only):**
|
|
115
|
+
|
|
116
|
+
```
|
|
117
|
+
pg_explain_query pg_get_schema_info pg_get_indexes
|
|
118
|
+
pg_get_constraints pg_get_functions pg_get_triggers
|
|
119
|
+
pg_get_rls_policies pg_get_enums pg_get_setup_instructions
|
|
120
|
+
pg_get_slow_queries pg_get_query_stats pg_get_user_permissions
|
|
121
|
+
pg_analyze_database pg_analyze_index_usage pg_monitor_database
|
|
122
|
+
pg_debug_database
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
> 💡 **Tip:** While this MCP is secure and customizable via tools, for maximum safety, pair the read-only tool set with a database user that only has `SELECT` privileges.
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## 🧰 Available tools
|
|
130
|
+
|
|
131
|
+
See the full list of available tools in the underlying server:
|
|
132
|
+
📦 [@henkey/postgres-mcp-server](https://github.com/HenkDz/postgresql-mcp-server)
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## 📄 License
|
|
137
|
+
|
|
138
|
+
MIT © [Edelcio Molina](https://github.com/edelciomolina)
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
/**
|
|
4
|
+
* @edelciomolina/postgres-mcp
|
|
5
|
+
* MCP server wrapper for PostgreSQL - reads credentials from .env at runtime.
|
|
6
|
+
*
|
|
7
|
+
* Author: Edelcio Molina <https://github.com/edelciomolina>
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
const child_process_1 = require("child_process");
|
|
11
|
+
const fs_1 = require("fs");
|
|
12
|
+
const os_1 = require("os");
|
|
13
|
+
const path_1 = require("path");
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
// Default read-only tools (no pg_execute_query / pg_manage_query)
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
const DEFAULT_READONLY_TOOLS = [
|
|
18
|
+
"pg_explain_query",
|
|
19
|
+
"pg_get_schema_info",
|
|
20
|
+
"pg_get_indexes",
|
|
21
|
+
"pg_get_constraints",
|
|
22
|
+
"pg_get_functions",
|
|
23
|
+
"pg_get_triggers",
|
|
24
|
+
"pg_get_rls_policies",
|
|
25
|
+
"pg_get_enums",
|
|
26
|
+
"pg_get_setup_instructions",
|
|
27
|
+
"pg_get_slow_queries",
|
|
28
|
+
"pg_get_query_stats",
|
|
29
|
+
"pg_get_user_permissions",
|
|
30
|
+
"pg_analyze_database",
|
|
31
|
+
"pg_analyze_index_usage",
|
|
32
|
+
"pg_monitor_database",
|
|
33
|
+
"pg_debug_database"
|
|
34
|
+
];
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
// .env reader
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
function loadEnvFile(envPath) {
|
|
39
|
+
if (!(0, fs_1.existsSync)(envPath)) {
|
|
40
|
+
throw new Error(`.env file not found at: ${envPath}`);
|
|
41
|
+
}
|
|
42
|
+
const env = {};
|
|
43
|
+
for (const line of (0, fs_1.readFileSync)(envPath, "utf8").split("\n")) {
|
|
44
|
+
const trimmed = line.trim();
|
|
45
|
+
if (!trimmed || trimmed.startsWith("#"))
|
|
46
|
+
continue;
|
|
47
|
+
const eqIdx = trimmed.indexOf("=");
|
|
48
|
+
if (eqIdx === -1)
|
|
49
|
+
continue;
|
|
50
|
+
const key = trimmed.slice(0, eqIdx).trim();
|
|
51
|
+
const value = trimmed
|
|
52
|
+
.slice(eqIdx + 1)
|
|
53
|
+
.trim()
|
|
54
|
+
.replace(/^["']|["']$/g, "");
|
|
55
|
+
env[key] = value;
|
|
56
|
+
}
|
|
57
|
+
return env;
|
|
58
|
+
}
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
// Resolve credential from .env using MCP_KEY_* mapping
|
|
61
|
+
// ---------------------------------------------------------------------------
|
|
62
|
+
function resolveCredential(envVars, dotenv, mcpKey, fallback) {
|
|
63
|
+
const mappedKey = envVars[mcpKey] ?? fallback;
|
|
64
|
+
const value = dotenv[mappedKey] ?? "";
|
|
65
|
+
return value;
|
|
66
|
+
}
|
|
67
|
+
// ---------------------------------------------------------------------------
|
|
68
|
+
// Build PostgreSQL connection string with URL-encoded password
|
|
69
|
+
// ---------------------------------------------------------------------------
|
|
70
|
+
function buildConnectionString(creds) {
|
|
71
|
+
const encodedUser = encodeURIComponent(creds.user);
|
|
72
|
+
const encodedPass = encodeURIComponent(creds.pass);
|
|
73
|
+
return `postgresql://${encodedUser}:${encodedPass}@${creds.host}:${creds.port}/${creds.name}?sslmode=${creds.sslmode}`;
|
|
74
|
+
}
|
|
75
|
+
// ---------------------------------------------------------------------------
|
|
76
|
+
// Main
|
|
77
|
+
// ---------------------------------------------------------------------------
|
|
78
|
+
function main() {
|
|
79
|
+
// Locate .env - walk up from cwd to find it
|
|
80
|
+
const cwd = process.cwd();
|
|
81
|
+
const envPath = (0, path_1.resolve)(cwd, ".env");
|
|
82
|
+
let dotenv;
|
|
83
|
+
try {
|
|
84
|
+
dotenv = loadEnvFile(envPath);
|
|
85
|
+
}
|
|
86
|
+
catch (err) {
|
|
87
|
+
process.stderr.write(`ERROR: ${err.message}\n`);
|
|
88
|
+
process.exit(1);
|
|
89
|
+
}
|
|
90
|
+
const envVars = process.env;
|
|
91
|
+
const host = resolveCredential(envVars, dotenv, "MCP_KEY_HOST", "DB_HOST");
|
|
92
|
+
const port = resolveCredential(envVars, dotenv, "MCP_KEY_PORT", "DB_PORT");
|
|
93
|
+
const name = resolveCredential(envVars, dotenv, "MCP_KEY_NAME", "DB_NAME");
|
|
94
|
+
const sslmode = resolveCredential(envVars, dotenv, "MCP_KEY_SSLMODE", "DB_SSLMODE");
|
|
95
|
+
const user = resolveCredential(envVars, dotenv, "MCP_KEY_USER", "DB_USER");
|
|
96
|
+
const pass = resolveCredential(envVars, dotenv, "MCP_KEY_PASS", "DB_PASS");
|
|
97
|
+
const missing = [];
|
|
98
|
+
if (!host)
|
|
99
|
+
missing.push(envVars["MCP_KEY_HOST"] ?? "DB_HOST");
|
|
100
|
+
if (!port)
|
|
101
|
+
missing.push(envVars["MCP_KEY_PORT"] ?? "DB_PORT");
|
|
102
|
+
if (!name)
|
|
103
|
+
missing.push(envVars["MCP_KEY_NAME"] ?? "DB_NAME");
|
|
104
|
+
if (!sslmode)
|
|
105
|
+
missing.push(envVars["MCP_KEY_SSLMODE"] ?? "DB_SSLMODE");
|
|
106
|
+
if (!user)
|
|
107
|
+
missing.push(envVars["MCP_KEY_USER"] ?? "DB_USER");
|
|
108
|
+
if (!pass)
|
|
109
|
+
missing.push(envVars["MCP_KEY_PASS"] ?? "DB_PASS");
|
|
110
|
+
if (missing.length > 0) {
|
|
111
|
+
process.stderr.write(`ERROR: Missing keys in .env: ${missing.join(", ")}\n`);
|
|
112
|
+
process.stderr.write(`Check the mapping in mcp.json (env field > MCP_KEY_*) and the .env file.\n`);
|
|
113
|
+
process.exit(1);
|
|
114
|
+
}
|
|
115
|
+
// Collect tools from args: tool=<name>
|
|
116
|
+
const tools = process.argv
|
|
117
|
+
.slice(2)
|
|
118
|
+
.filter((a) => a.startsWith("tool="))
|
|
119
|
+
.map((a) => a.slice(5));
|
|
120
|
+
const enabledTools = tools.length > 0 ? tools : DEFAULT_READONLY_TOOLS;
|
|
121
|
+
// Write temp tools config
|
|
122
|
+
const toolsFile = (0, path_1.join)((0, os_1.tmpdir)(), `mcp-pg-tools-${process.pid}.json`);
|
|
123
|
+
(0, fs_1.writeFileSync)(toolsFile, JSON.stringify({ enabledTools }, null, 2));
|
|
124
|
+
const connStr = buildConnectionString({
|
|
125
|
+
host,
|
|
126
|
+
port,
|
|
127
|
+
name,
|
|
128
|
+
sslmode,
|
|
129
|
+
user,
|
|
130
|
+
pass
|
|
131
|
+
});
|
|
132
|
+
process.env["POSTGRES_CONNECTION_STRING"] = connStr;
|
|
133
|
+
process.env["NODE_TLS_REJECT_UNAUTHORIZED"] = "0";
|
|
134
|
+
try {
|
|
135
|
+
(0, child_process_1.execFileSync)("npx", ["-y", "@henkey/postgres-mcp-server", "--tools-config", toolsFile], { stdio: "inherit", env: process.env });
|
|
136
|
+
}
|
|
137
|
+
finally {
|
|
138
|
+
try {
|
|
139
|
+
(0, fs_1.unlinkSync)(toolsFile);
|
|
140
|
+
}
|
|
141
|
+
catch { }
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
main();
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@edelciomolina/postgres-mcp",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "MCP server wrapper for PostgreSQL - reads credentials from .env with configurable key mapping and safe read-only defaults.",
|
|
5
|
+
"bin": {
|
|
6
|
+
"postgres-mcp": "./dist/index.js"
|
|
7
|
+
},
|
|
8
|
+
"files": [
|
|
9
|
+
"dist/",
|
|
10
|
+
"README.md"
|
|
11
|
+
],
|
|
12
|
+
"scripts": {
|
|
13
|
+
"build": "tsc",
|
|
14
|
+
"prepublishOnly": "npm run build"
|
|
15
|
+
},
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"@henkey/postgres-mcp-server": "^1.0.5"
|
|
18
|
+
},
|
|
19
|
+
"devDependencies": {
|
|
20
|
+
"@types/node": "^20.19.42",
|
|
21
|
+
"typescript": "^5.0.0"
|
|
22
|
+
},
|
|
23
|
+
"engines": {
|
|
24
|
+
"node": ">=18"
|
|
25
|
+
},
|
|
26
|
+
"author": "Edelcio Molina <https://github.com/edelciomolina>",
|
|
27
|
+
"license": "MIT",
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "https://github.com/edelciomolina/postgres-mcp.git"
|
|
31
|
+
},
|
|
32
|
+
"homepage": "https://github.com/edelciomolina/postgres-mcp#readme",
|
|
33
|
+
"keywords": [
|
|
34
|
+
"mcp",
|
|
35
|
+
"postgresql",
|
|
36
|
+
"postgres",
|
|
37
|
+
"database",
|
|
38
|
+
"model-context-protocol",
|
|
39
|
+
"copilot",
|
|
40
|
+
"ai",
|
|
41
|
+
"vscode"
|
|
42
|
+
]
|
|
43
|
+
}
|