@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 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
+ [![npm version](https://img.shields.io/npm/v/@edelciomolina/postgres-mcp)](https://www.npmjs.com/package/@edelciomolina/postgres-mcp)
6
+ [![license](https://img.shields.io/npm/l/@edelciomolina/postgres-mcp)](./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)
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * @edelciomolina/postgres-mcp
4
+ * MCP server wrapper for PostgreSQL - reads credentials from .env at runtime.
5
+ *
6
+ * Author: Edelcio Molina <https://github.com/edelciomolina>
7
+ */
8
+ export {};
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
+ }