@achmadya-dev/mcp-postgres-query 0.2.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/LICENSE +21 -0
- package/README.md +129 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/dist/postgres/config.d.ts +14 -0
- package/dist/postgres/config.d.ts.map +1 -0
- package/dist/postgres/config.js +14 -0
- package/dist/postgres/config.js.map +1 -0
- package/dist/postgres/helpers.d.ts +26 -0
- package/dist/postgres/helpers.d.ts.map +1 -0
- package/dist/postgres/helpers.js +223 -0
- package/dist/postgres/helpers.js.map +1 -0
- package/dist/postgres/postgres.d.ts +16 -0
- package/dist/postgres/postgres.d.ts.map +1 -0
- package/dist/postgres/postgres.js +52 -0
- package/dist/postgres/postgres.js.map +1 -0
- package/dist/postgres/schema.d.ts +32 -0
- package/dist/postgres/schema.d.ts.map +1 -0
- package/dist/postgres/schema.js +42 -0
- package/dist/postgres/schema.js.map +1 -0
- package/dist/tools/postgres_ddl.d.ts +2 -0
- package/dist/tools/postgres_ddl.d.ts.map +1 -0
- package/dist/tools/postgres_ddl.js +32 -0
- package/dist/tools/postgres_ddl.js.map +1 -0
- package/dist/tools/postgres_delete.d.ts +2 -0
- package/dist/tools/postgres_delete.d.ts.map +1 -0
- package/dist/tools/postgres_delete.js +23 -0
- package/dist/tools/postgres_delete.js.map +1 -0
- package/dist/tools/postgres_insert.d.ts +2 -0
- package/dist/tools/postgres_insert.d.ts.map +1 -0
- package/dist/tools/postgres_insert.js +23 -0
- package/dist/tools/postgres_insert.js.map +1 -0
- package/dist/tools/postgres_select.d.ts +2 -0
- package/dist/tools/postgres_select.d.ts.map +1 -0
- package/dist/tools/postgres_select.js +19 -0
- package/dist/tools/postgres_select.js.map +1 -0
- package/dist/tools/postgres_update.d.ts +2 -0
- package/dist/tools/postgres_update.d.ts.map +1 -0
- package/dist/tools/postgres_update.js +23 -0
- package/dist/tools/postgres_update.js.map +1 -0
- package/package.json +56 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 achmadya
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# @achmadya-dev/mcp-postgres-query
|
|
2
|
+
|
|
3
|
+
MCP server for PostgreSQL. Runs a single SQL statement per tool call over **stdio**. **Read-only by default** — writes and DDL require explicit env flags.
|
|
4
|
+
|
|
5
|
+
## Requirements
|
|
6
|
+
|
|
7
|
+
- Node.js **≥ 20**
|
|
8
|
+
- A reachable PostgreSQL server
|
|
9
|
+
|
|
10
|
+
## Install from npm
|
|
11
|
+
|
|
12
|
+
```json
|
|
13
|
+
{
|
|
14
|
+
"mcpServers": {
|
|
15
|
+
"postgres": {
|
|
16
|
+
"command": "npx",
|
|
17
|
+
"args": ["-y", "@achmadya-dev/mcp-postgres-query"],
|
|
18
|
+
"env": {
|
|
19
|
+
"POSTGRES_HOST": "localhost",
|
|
20
|
+
"POSTGRES_PORT": "5432",
|
|
21
|
+
"POSTGRES_USER": "your_user",
|
|
22
|
+
"POSTGRES_PASSWORD": "your_password",
|
|
23
|
+
"POSTGRES_DATABASE": "your_database"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Or use `envFile` instead of inline `env` (see [Cursor MCP docs](https://cursor.com/docs/mcp)).
|
|
31
|
+
|
|
32
|
+
## Develop from source
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
cp .env.example .env
|
|
36
|
+
pnpm install
|
|
37
|
+
docker compose up -d postgres
|
|
38
|
+
pnpm --filter @achmadya-dev/mcp-postgres-query run build
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
`.cursor/mcp.json`:
|
|
42
|
+
|
|
43
|
+
```json
|
|
44
|
+
{
|
|
45
|
+
"mcpServers": {
|
|
46
|
+
"postgres": {
|
|
47
|
+
"command": "node",
|
|
48
|
+
"args": ["${workspaceFolder}/packages/mcp-postgres-query/dist/index.js"],
|
|
49
|
+
"envFile": "${workspaceFolder}/.env"
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Relevant `.env` keys:
|
|
56
|
+
|
|
57
|
+
```env
|
|
58
|
+
POSTGRES_HOST=localhost
|
|
59
|
+
POSTGRES_PORT=5432
|
|
60
|
+
POSTGRES_USER=dev
|
|
61
|
+
POSTGRES_PASSWORD=devpassword
|
|
62
|
+
POSTGRES_DATABASE=devdb
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Environment variables
|
|
66
|
+
|
|
67
|
+
### Connection
|
|
68
|
+
|
|
69
|
+
| Variable | Default | Description |
|
|
70
|
+
| ------------------- | ------------ | ---------------------------------- |
|
|
71
|
+
| `POSTGRES_HOST` | `localhost` | PostgreSQL host |
|
|
72
|
+
| `POSTGRES_PORT` | `5432` | Port |
|
|
73
|
+
| `POSTGRES_USER` | _(empty)_ | Username |
|
|
74
|
+
| `POSTGRES_PASSWORD` | _(empty)_ | Password |
|
|
75
|
+
| `POSTGRES_DATABASE` | _(optional)_ | Default database |
|
|
76
|
+
| `POSTGRES_MAX_ROWS` | `500` | Max rows for row-returning queries |
|
|
77
|
+
|
|
78
|
+
### Write access
|
|
79
|
+
|
|
80
|
+
| Variable | Allows |
|
|
81
|
+
| ------------------------ | -------- |
|
|
82
|
+
| `ALLOW_INSERT_OPERATION` | `INSERT` |
|
|
83
|
+
| `ALLOW_UPDATE_OPERATION` | `UPDATE` |
|
|
84
|
+
| `ALLOW_DELETE_OPERATION` | `DELETE` |
|
|
85
|
+
| `ALLOW_DDL_OPERATION` | DDL |
|
|
86
|
+
|
|
87
|
+
Enabled values: `true`, `1`, `yes`, `on`.
|
|
88
|
+
|
|
89
|
+
## Tools
|
|
90
|
+
|
|
91
|
+
| Tool | Statements | Env flag |
|
|
92
|
+
| ----------------- | ---------------------------------------------- | ------------------------ |
|
|
93
|
+
| `postgres_select` | `SELECT`, `EXPLAIN`, `TABLE`, `VALUES`, `SHOW` | always on |
|
|
94
|
+
| `postgres_insert` | `INSERT` | `ALLOW_INSERT_OPERATION` |
|
|
95
|
+
| `postgres_update` | `UPDATE` | `ALLOW_UPDATE_OPERATION` |
|
|
96
|
+
| `postgres_delete` | `DELETE` | `ALLOW_DELETE_OPERATION` |
|
|
97
|
+
| `postgres_ddl` | DDL | `ALLOW_DDL_OPERATION` |
|
|
98
|
+
|
|
99
|
+
### Tool input
|
|
100
|
+
|
|
101
|
+
| Parameter | Required | Description |
|
|
102
|
+
| ---------- | -------- | ------------------------------------------- |
|
|
103
|
+
| `sql` | yes | Single SQL statement |
|
|
104
|
+
| `database` | no | Overrides `POSTGRES_DATABASE` for this call |
|
|
105
|
+
|
|
106
|
+
Example:
|
|
107
|
+
|
|
108
|
+
```json
|
|
109
|
+
{
|
|
110
|
+
"sql": "SELECT datname FROM pg_database",
|
|
111
|
+
"database": "postgres"
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Use `schema.table` in `sql` for other schemas in the same database.
|
|
116
|
+
|
|
117
|
+
## Behavior and security
|
|
118
|
+
|
|
119
|
+
- One statement per request.
|
|
120
|
+
- Results are JSON text with `columns`, `rows`, `rowCount`, `truncated`, `maxRows` for row-returning queries.
|
|
121
|
+
- Execute results return `kind: "execute"` with `affectedRows`.
|
|
122
|
+
|
|
123
|
+
## Package scripts
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
pnpm run build
|
|
127
|
+
pnpm test
|
|
128
|
+
pnpm start
|
|
129
|
+
```
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { startMcpServer } from "@achmadya-dev/mcp-core";
|
|
3
|
+
import packageJson from "../package.json" with { type: "json" };
|
|
4
|
+
import { postgres_ddl } from "./tools/postgres_ddl.js";
|
|
5
|
+
import { postgres_delete } from "./tools/postgres_delete.js";
|
|
6
|
+
import { postgres_insert } from "./tools/postgres_insert.js";
|
|
7
|
+
import { postgres_select } from "./tools/postgres_select.js";
|
|
8
|
+
import { postgres_update } from "./tools/postgres_update.js";
|
|
9
|
+
await startMcpServer({
|
|
10
|
+
name: "PostgreSQL Database",
|
|
11
|
+
version: packageJson.version,
|
|
12
|
+
tools: [postgres_select, postgres_insert, postgres_update, postgres_delete, postgres_ddl],
|
|
13
|
+
});
|
|
14
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,WAAW,MAAM,iBAAiB,CAAC,OAAO,IAAI,EAAE,MAAM,EAAE,CAAC;AAChE,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AACvD,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAC7D,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAC7D,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAC7D,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAE7D,MAAM,cAAc,CAAC;IACnB,IAAI,EAAE,qBAAqB;IAC3B,OAAO,EAAE,WAAW,CAAC,OAAO;IAC5B,KAAK,EAAE,CAAC,eAAe,EAAE,eAAe,EAAE,eAAe,EAAE,eAAe,EAAE,YAAY,CAAC;CAC1F,CAAC,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
declare const _default: {
|
|
2
|
+
host: string;
|
|
3
|
+
port: number;
|
|
4
|
+
user: string;
|
|
5
|
+
password: string;
|
|
6
|
+
database: string | undefined;
|
|
7
|
+
maxRows: number;
|
|
8
|
+
allowInsert: boolean;
|
|
9
|
+
allowUpdate: boolean;
|
|
10
|
+
allowDelete: boolean;
|
|
11
|
+
allowDdl: boolean;
|
|
12
|
+
};
|
|
13
|
+
export default _default;
|
|
14
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/postgres/config.ts"],"names":[],"mappings":";;;;;;;;;;;;AAEA,wBAWE"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { envBool, envInt, envStr } from "@achmadya-dev/mcp-core";
|
|
2
|
+
export default {
|
|
3
|
+
host: envStr("POSTGRES_HOST", "localhost"),
|
|
4
|
+
port: envInt("POSTGRES_PORT", 5432),
|
|
5
|
+
user: envStr("POSTGRES_USER"),
|
|
6
|
+
password: envStr("POSTGRES_PASSWORD", ""),
|
|
7
|
+
database: envStr("POSTGRES_DATABASE") || undefined,
|
|
8
|
+
maxRows: envInt("POSTGRES_MAX_ROWS", 500),
|
|
9
|
+
allowInsert: envBool("ALLOW_INSERT_OPERATION"),
|
|
10
|
+
allowUpdate: envBool("ALLOW_UPDATE_OPERATION"),
|
|
11
|
+
allowDelete: envBool("ALLOW_DELETE_OPERATION"),
|
|
12
|
+
allowDdl: envBool("ALLOW_DDL_OPERATION"),
|
|
13
|
+
};
|
|
14
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/postgres/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AAEjE,eAAe;IACb,IAAI,EAAE,MAAM,CAAC,eAAe,EAAE,WAAW,CAAC;IAC1C,IAAI,EAAE,MAAM,CAAC,eAAe,EAAE,IAAI,CAAC;IACnC,IAAI,EAAE,MAAM,CAAC,eAAe,CAAC;IAC7B,QAAQ,EAAE,MAAM,CAAC,mBAAmB,EAAE,EAAE,CAAC;IACzC,QAAQ,EAAE,MAAM,CAAC,mBAAmB,CAAC,IAAI,SAAS;IAClD,OAAO,EAAE,MAAM,CAAC,mBAAmB,EAAE,GAAG,CAAC;IACzC,WAAW,EAAE,OAAO,CAAC,wBAAwB,CAAC;IAC9C,WAAW,EAAE,OAAO,CAAC,wBAAwB,CAAC;IAC9C,WAAW,EAAE,OAAO,CAAC,wBAAwB,CAAC;IAC9C,QAAQ,EAAE,OAAO,CAAC,qBAAqB,CAAC;CACzC,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validate a PostgreSQL database name for connection selection.
|
|
3
|
+
*/
|
|
4
|
+
export declare function validateDatabaseName(name: string): string;
|
|
5
|
+
/**
|
|
6
|
+
* Resolve which database to connect to.
|
|
7
|
+
* The per-request database parameter overrides POSTGRES_DATABASE when provided.
|
|
8
|
+
*/
|
|
9
|
+
export declare function resolveDatabase(configured: string | undefined, requested: string | undefined): string;
|
|
10
|
+
/**
|
|
11
|
+
* Validate and clean up input SQL query and allowed prefixes.
|
|
12
|
+
*/
|
|
13
|
+
export declare function validateInputs(sql: string, allowedPrefixes: string[]): {
|
|
14
|
+
cleanSql: string;
|
|
15
|
+
prefixes: string[];
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Splits the SQL query, parses syntax, handles quoting/nesting/comments,
|
|
19
|
+
* and extracts the single SQL statement while ensuring syntax correctness.
|
|
20
|
+
*/
|
|
21
|
+
export declare function parseSingleStatement(sql: string): string;
|
|
22
|
+
/**
|
|
23
|
+
* Validates the statement prefix and detects dangerous SQL patterns (hardening).
|
|
24
|
+
*/
|
|
25
|
+
export declare function validateStatement(statement: string, prefixes: string[]): void;
|
|
26
|
+
//# sourceMappingURL=helpers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../src/postgres/helpers.ts"],"names":[],"mappings":"AAIA;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAczD;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,SAAS,EAAE,MAAM,GAAG,SAAS,GAC5B,MAAM,CAYR;AAED;;GAEG;AACH,wBAAgB,cAAc,CAC5B,GAAG,EAAE,MAAM,EACX,eAAe,EAAE,MAAM,EAAE,GACxB;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,EAAE,CAAA;CAAE,CAY1C;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAmIxD;AAiCD;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,IAAI,CA+B7E"}
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import { ToolError } from "@achmadya-dev/mcp-core";
|
|
2
|
+
const DATABASE_NAME_RE = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
|
|
3
|
+
/**
|
|
4
|
+
* Validate a PostgreSQL database name for connection selection.
|
|
5
|
+
*/
|
|
6
|
+
export function validateDatabaseName(name) {
|
|
7
|
+
const trimmed = name.trim();
|
|
8
|
+
if (!trimmed) {
|
|
9
|
+
throw new ToolError("Database name cannot be empty.");
|
|
10
|
+
}
|
|
11
|
+
if (trimmed.length > 63) {
|
|
12
|
+
throw new ToolError("Database name is too long (max 63 characters).");
|
|
13
|
+
}
|
|
14
|
+
if (!DATABASE_NAME_RE.test(trimmed)) {
|
|
15
|
+
throw new ToolError("Invalid database name. Use letters, digits, and underscores; must start with a letter or underscore.");
|
|
16
|
+
}
|
|
17
|
+
return trimmed;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Resolve which database to connect to.
|
|
21
|
+
* The per-request database parameter overrides POSTGRES_DATABASE when provided.
|
|
22
|
+
*/
|
|
23
|
+
export function resolveDatabase(configured, requested) {
|
|
24
|
+
if (requested !== undefined) {
|
|
25
|
+
return validateDatabaseName(requested);
|
|
26
|
+
}
|
|
27
|
+
if (configured !== undefined) {
|
|
28
|
+
return configured;
|
|
29
|
+
}
|
|
30
|
+
throw new ToolError("No database specified. Set POSTGRES_DATABASE in server config or pass the database parameter.");
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Validate and clean up input SQL query and allowed prefixes.
|
|
34
|
+
*/
|
|
35
|
+
export function validateInputs(sql, allowedPrefixes) {
|
|
36
|
+
const cleanSql = sql.trim();
|
|
37
|
+
if (!cleanSql) {
|
|
38
|
+
throw new ToolError("SQL query cannot be empty.");
|
|
39
|
+
}
|
|
40
|
+
if (allowedPrefixes.length === 0) {
|
|
41
|
+
throw new ToolError("allowedPrefixes cannot be empty.");
|
|
42
|
+
}
|
|
43
|
+
const prefixes = allowedPrefixes.map((p) => p.trim().toUpperCase());
|
|
44
|
+
return { cleanSql, prefixes };
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Splits the SQL query, parses syntax, handles quoting/nesting/comments,
|
|
48
|
+
* and extracts the single SQL statement while ensuring syntax correctness.
|
|
49
|
+
*/
|
|
50
|
+
export function parseSingleStatement(sql) {
|
|
51
|
+
const parts = [];
|
|
52
|
+
let current = "";
|
|
53
|
+
let inSingleQuote = false;
|
|
54
|
+
let inDoubleQuote = false;
|
|
55
|
+
let inBacktick = false;
|
|
56
|
+
let inBracketIdentifier = false;
|
|
57
|
+
let inLineComment = false;
|
|
58
|
+
let inBlockComment = false;
|
|
59
|
+
let escape = false;
|
|
60
|
+
for (let i = 0; i < sql.length; i++) {
|
|
61
|
+
const char = sql[i];
|
|
62
|
+
const next = sql[i + 1];
|
|
63
|
+
if (escape) {
|
|
64
|
+
current += char;
|
|
65
|
+
escape = false;
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
if (char === "\\" && (inSingleQuote || inDoubleQuote || inBacktick)) {
|
|
69
|
+
current += char;
|
|
70
|
+
escape = true;
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
if (inLineComment) {
|
|
74
|
+
current += char;
|
|
75
|
+
if (char === "\n") {
|
|
76
|
+
inLineComment = false;
|
|
77
|
+
}
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
if (inBlockComment) {
|
|
81
|
+
current += char;
|
|
82
|
+
if (char === "*" && next === "/") {
|
|
83
|
+
current += next;
|
|
84
|
+
i++;
|
|
85
|
+
inBlockComment = false;
|
|
86
|
+
}
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
if (!inSingleQuote && !inDoubleQuote && !inBacktick && !inBracketIdentifier) {
|
|
90
|
+
if (char === "-" && next === "-") {
|
|
91
|
+
current += char + next;
|
|
92
|
+
i++;
|
|
93
|
+
inLineComment = true;
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
if (char === "/" && next === "*") {
|
|
97
|
+
current += char + next;
|
|
98
|
+
i++;
|
|
99
|
+
inBlockComment = true;
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
if (char === "'" && !inDoubleQuote && !inBacktick && !inBracketIdentifier) {
|
|
104
|
+
if (inSingleQuote && next === "'") {
|
|
105
|
+
current += "''";
|
|
106
|
+
i++;
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
inSingleQuote = !inSingleQuote;
|
|
110
|
+
current += char;
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
if (char === '"' && !inSingleQuote && !inBacktick && !inBracketIdentifier) {
|
|
114
|
+
if (inDoubleQuote && next === '"') {
|
|
115
|
+
current += '""';
|
|
116
|
+
i++;
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
inDoubleQuote = !inDoubleQuote;
|
|
120
|
+
current += char;
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
if (char === "`" && !inSingleQuote && !inDoubleQuote && !inBracketIdentifier) {
|
|
124
|
+
inBacktick = !inBacktick;
|
|
125
|
+
current += char;
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
if (char === "[" && !inSingleQuote && !inDoubleQuote && !inBacktick) {
|
|
129
|
+
inBracketIdentifier = true;
|
|
130
|
+
current += char;
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
if (char === "]" && inBracketIdentifier) {
|
|
134
|
+
inBracketIdentifier = false;
|
|
135
|
+
current += char;
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
if (char === ";" && !inSingleQuote && !inDoubleQuote && !inBacktick && !inBracketIdentifier) {
|
|
139
|
+
const trimmed = current.trim();
|
|
140
|
+
if (trimmed.length > 0) {
|
|
141
|
+
parts.push(trimmed);
|
|
142
|
+
}
|
|
143
|
+
current = "";
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
current += char;
|
|
147
|
+
}
|
|
148
|
+
if (inSingleQuote)
|
|
149
|
+
throw new ToolError("Unterminated single quote string.");
|
|
150
|
+
if (inDoubleQuote)
|
|
151
|
+
throw new ToolError("Unterminated double quote identifier.");
|
|
152
|
+
if (inBacktick)
|
|
153
|
+
throw new ToolError("Unterminated backtick identifier.");
|
|
154
|
+
if (inBracketIdentifier)
|
|
155
|
+
throw new ToolError("Unterminated bracket identifier.");
|
|
156
|
+
if (inBlockComment)
|
|
157
|
+
throw new ToolError("Unterminated block comment.");
|
|
158
|
+
const last = current.trim();
|
|
159
|
+
if (last.length > 0) {
|
|
160
|
+
parts.push(last);
|
|
161
|
+
}
|
|
162
|
+
if (parts.length !== 1) {
|
|
163
|
+
throw new ToolError("Only a single SQL query is allowed per call.");
|
|
164
|
+
}
|
|
165
|
+
return parts[0];
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Recursively strips leading single-line and block comments from a SQL statement.
|
|
169
|
+
*/
|
|
170
|
+
function stripLeadingComments(sql) {
|
|
171
|
+
let result = sql.trim();
|
|
172
|
+
while (true) {
|
|
173
|
+
if (result.startsWith("--")) {
|
|
174
|
+
const newlineIndex = result.indexOf("\n");
|
|
175
|
+
if (newlineIndex === -1) {
|
|
176
|
+
return "";
|
|
177
|
+
}
|
|
178
|
+
result = result.slice(newlineIndex + 1).trim();
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
if (result.startsWith("/*")) {
|
|
182
|
+
const endIndex = result.indexOf("*/");
|
|
183
|
+
if (endIndex === -1) {
|
|
184
|
+
throw new ToolError("Unterminated leading block comment.");
|
|
185
|
+
}
|
|
186
|
+
result = result.slice(endIndex + 2).trim();
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
break;
|
|
190
|
+
}
|
|
191
|
+
return result;
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Validates the statement prefix and detects dangerous SQL patterns (hardening).
|
|
195
|
+
*/
|
|
196
|
+
export function validateStatement(statement, prefixes) {
|
|
197
|
+
const stripped = stripLeadingComments(statement);
|
|
198
|
+
const match = stripped.match(/^([A-Z]+)/i);
|
|
199
|
+
if (!match) {
|
|
200
|
+
throw new ToolError("Unable to determine SQL statement type.");
|
|
201
|
+
}
|
|
202
|
+
const firstKeyword = match[1].toUpperCase();
|
|
203
|
+
if (!prefixes.includes(firstKeyword)) {
|
|
204
|
+
throw new ToolError(`SQL query is not allowed for this tool. Allowed statements: ${prefixes.join(", ")}`);
|
|
205
|
+
}
|
|
206
|
+
const dangerousPatterns = [
|
|
207
|
+
/\bXP_CMDSHELL\b/i,
|
|
208
|
+
/\bEXEC\b/i,
|
|
209
|
+
/\bEXECUTE\b/i,
|
|
210
|
+
/\bPREPARE\b/i,
|
|
211
|
+
/\bDEALLOCATE\b/i,
|
|
212
|
+
/\bATTACH\s+DATABASE\b/i,
|
|
213
|
+
/\bCOPY\s+.*\s+PROGRAM\b/i,
|
|
214
|
+
/\bLOAD_FILE\s*\(/i,
|
|
215
|
+
/\bINTO\s+OUTFILE\b/i,
|
|
216
|
+
];
|
|
217
|
+
for (const pattern of dangerousPatterns) {
|
|
218
|
+
if (pattern.test(statement)) {
|
|
219
|
+
throw new ToolError(`Dangerous SQL pattern detected: ${pattern}`);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
//# sourceMappingURL=helpers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"helpers.js","sourceRoot":"","sources":["../../src/postgres/helpers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAEnD,MAAM,gBAAgB,GAAG,0BAA0B,CAAC;AAEpD;;GAEG;AACH,MAAM,UAAU,oBAAoB,CAAC,IAAY;IAC/C,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;IAC5B,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,SAAS,CAAC,gCAAgC,CAAC,CAAC;IACxD,CAAC;IACD,IAAI,OAAO,CAAC,MAAM,GAAG,EAAE,EAAE,CAAC;QACxB,MAAM,IAAI,SAAS,CAAC,gDAAgD,CAAC,CAAC;IACxE,CAAC;IACD,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;QACpC,MAAM,IAAI,SAAS,CACjB,sGAAsG,CACvG,CAAC;IACJ,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,eAAe,CAC7B,UAA8B,EAC9B,SAA6B;IAE7B,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;QAC5B,OAAO,oBAAoB,CAAC,SAAS,CAAC,CAAC;IACzC,CAAC;IAED,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;QAC7B,OAAO,UAAU,CAAC;IACpB,CAAC;IAED,MAAM,IAAI,SAAS,CACjB,+FAA+F,CAChG,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,cAAc,CAC5B,GAAW,EACX,eAAyB;IAEzB,MAAM,QAAQ,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;IAC5B,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,MAAM,IAAI,SAAS,CAAC,4BAA4B,CAAC,CAAC;IACpD,CAAC;IAED,IAAI,eAAe,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACjC,MAAM,IAAI,SAAS,CAAC,kCAAkC,CAAC,CAAC;IAC1D,CAAC;IAED,MAAM,QAAQ,GAAG,eAAe,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,CAAC;IACpE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC;AAChC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,oBAAoB,CAAC,GAAW;IAC9C,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,IAAI,OAAO,GAAG,EAAE,CAAC;IAEjB,IAAI,aAAa,GAAG,KAAK,CAAC;IAC1B,IAAI,aAAa,GAAG,KAAK,CAAC;IAC1B,IAAI,UAAU,GAAG,KAAK,CAAC;IACvB,IAAI,mBAAmB,GAAG,KAAK,CAAC;IAEhC,IAAI,aAAa,GAAG,KAAK,CAAC;IAC1B,IAAI,cAAc,GAAG,KAAK,CAAC;IAC3B,IAAI,MAAM,GAAG,KAAK,CAAC;IAEnB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACpC,MAAM,IAAI,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC;QACpB,MAAM,IAAI,GAAG,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QAExB,IAAI,MAAM,EAAE,CAAC;YACX,OAAO,IAAI,IAAI,CAAC;YAChB,MAAM,GAAG,KAAK,CAAC;YACf,SAAS;QACX,CAAC;QAED,IAAI,IAAI,KAAK,IAAI,IAAI,CAAC,aAAa,IAAI,aAAa,IAAI,UAAU,CAAC,EAAE,CAAC;YACpE,OAAO,IAAI,IAAI,CAAC;YAChB,MAAM,GAAG,IAAI,CAAC;YACd,SAAS;QACX,CAAC;QAED,IAAI,aAAa,EAAE,CAAC;YAClB,OAAO,IAAI,IAAI,CAAC;YAChB,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;gBAClB,aAAa,GAAG,KAAK,CAAC;YACxB,CAAC;YACD,SAAS;QACX,CAAC;QAED,IAAI,cAAc,EAAE,CAAC;YACnB,OAAO,IAAI,IAAI,CAAC;YAChB,IAAI,IAAI,KAAK,GAAG,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC;gBACjC,OAAO,IAAI,IAAI,CAAC;gBAChB,CAAC,EAAE,CAAC;gBACJ,cAAc,GAAG,KAAK,CAAC;YACzB,CAAC;YACD,SAAS;QACX,CAAC;QAED,IAAI,CAAC,aAAa,IAAI,CAAC,aAAa,IAAI,CAAC,UAAU,IAAI,CAAC,mBAAmB,EAAE,CAAC;YAC5E,IAAI,IAAI,KAAK,GAAG,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC;gBACjC,OAAO,IAAI,IAAI,GAAG,IAAI,CAAC;gBACvB,CAAC,EAAE,CAAC;gBACJ,aAAa,GAAG,IAAI,CAAC;gBACrB,SAAS;YACX,CAAC;YAED,IAAI,IAAI,KAAK,GAAG,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC;gBACjC,OAAO,IAAI,IAAI,GAAG,IAAI,CAAC;gBACvB,CAAC,EAAE,CAAC;gBACJ,cAAc,GAAG,IAAI,CAAC;gBACtB,SAAS;YACX,CAAC;QACH,CAAC;QAED,IAAI,IAAI,KAAK,GAAG,IAAI,CAAC,aAAa,IAAI,CAAC,UAAU,IAAI,CAAC,mBAAmB,EAAE,CAAC;YAC1E,IAAI,aAAa,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC;gBAClC,OAAO,IAAI,IAAI,CAAC;gBAChB,CAAC,EAAE,CAAC;gBACJ,SAAS;YACX,CAAC;YACD,aAAa,GAAG,CAAC,aAAa,CAAC;YAC/B,OAAO,IAAI,IAAI,CAAC;YAChB,SAAS;QACX,CAAC;QAED,IAAI,IAAI,KAAK,GAAG,IAAI,CAAC,aAAa,IAAI,CAAC,UAAU,IAAI,CAAC,mBAAmB,EAAE,CAAC;YAC1E,IAAI,aAAa,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC;gBAClC,OAAO,IAAI,IAAI,CAAC;gBAChB,CAAC,EAAE,CAAC;gBACJ,SAAS;YACX,CAAC;YACD,aAAa,GAAG,CAAC,aAAa,CAAC;YAC/B,OAAO,IAAI,IAAI,CAAC;YAChB,SAAS;QACX,CAAC;QAED,IAAI,IAAI,KAAK,GAAG,IAAI,CAAC,aAAa,IAAI,CAAC,aAAa,IAAI,CAAC,mBAAmB,EAAE,CAAC;YAC7E,UAAU,GAAG,CAAC,UAAU,CAAC;YACzB,OAAO,IAAI,IAAI,CAAC;YAChB,SAAS;QACX,CAAC;QAED,IAAI,IAAI,KAAK,GAAG,IAAI,CAAC,aAAa,IAAI,CAAC,aAAa,IAAI,CAAC,UAAU,EAAE,CAAC;YACpE,mBAAmB,GAAG,IAAI,CAAC;YAC3B,OAAO,IAAI,IAAI,CAAC;YAChB,SAAS;QACX,CAAC;QAED,IAAI,IAAI,KAAK,GAAG,IAAI,mBAAmB,EAAE,CAAC;YACxC,mBAAmB,GAAG,KAAK,CAAC;YAC5B,OAAO,IAAI,IAAI,CAAC;YAChB,SAAS;QACX,CAAC;QAED,IAAI,IAAI,KAAK,GAAG,IAAI,CAAC,aAAa,IAAI,CAAC,aAAa,IAAI,CAAC,UAAU,IAAI,CAAC,mBAAmB,EAAE,CAAC;YAC5F,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;YAC/B,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACvB,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YACtB,CAAC;YACD,OAAO,GAAG,EAAE,CAAC;YACb,SAAS;QACX,CAAC;QAED,OAAO,IAAI,IAAI,CAAC;IAClB,CAAC;IAED,IAAI,aAAa;QAAE,MAAM,IAAI,SAAS,CAAC,mCAAmC,CAAC,CAAC;IAC5E,IAAI,aAAa;QAAE,MAAM,IAAI,SAAS,CAAC,uCAAuC,CAAC,CAAC;IAChF,IAAI,UAAU;QAAE,MAAM,IAAI,SAAS,CAAC,mCAAmC,CAAC,CAAC;IACzE,IAAI,mBAAmB;QAAE,MAAM,IAAI,SAAS,CAAC,kCAAkC,CAAC,CAAC;IACjF,IAAI,cAAc;QAAE,MAAM,IAAI,SAAS,CAAC,6BAA6B,CAAC,CAAC;IAEvE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;IAC5B,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACpB,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACnB,CAAC;IAED,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,MAAM,IAAI,SAAS,CAAC,8CAA8C,CAAC,CAAC;IACtE,CAAC;IAED,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED;;GAEG;AACH,SAAS,oBAAoB,CAAC,GAAW;IACvC,IAAI,MAAM,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;IAExB,OAAO,IAAI,EAAE,CAAC;QACZ,IAAI,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;YAC5B,MAAM,YAAY,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;YAC1C,IAAI,YAAY,KAAK,CAAC,CAAC,EAAE,CAAC;gBACxB,OAAO,EAAE,CAAC;YACZ,CAAC;YACD,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,YAAY,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;YAC/C,SAAS;QACX,CAAC;QAED,IAAI,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;YAC5B,MAAM,QAAQ,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;YACtC,IAAI,QAAQ,KAAK,CAAC,CAAC,EAAE,CAAC;gBACpB,MAAM,IAAI,SAAS,CAAC,qCAAqC,CAAC,CAAC;YAC7D,CAAC;YACD,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;YAC3C,SAAS;QACX,CAAC;QAED,MAAM;IACR,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,iBAAiB,CAAC,SAAiB,EAAE,QAAkB;IACrE,MAAM,QAAQ,GAAG,oBAAoB,CAAC,SAAS,CAAC,CAAC;IACjD,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;IAC3C,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,SAAS,CAAC,yCAAyC,CAAC,CAAC;IACjE,CAAC;IAED,MAAM,YAAY,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;IAC5C,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC;QACrC,MAAM,IAAI,SAAS,CACjB,+DAA+D,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CACrF,CAAC;IACJ,CAAC;IAED,MAAM,iBAAiB,GAAa;QAClC,kBAAkB;QAClB,WAAW;QACX,cAAc;QACd,cAAc;QACd,iBAAiB;QACjB,wBAAwB;QACxB,0BAA0B;QAC1B,mBAAmB;QACnB,qBAAqB;KACtB,CAAC;IAEF,KAAK,MAAM,OAAO,IAAI,iBAAiB,EAAE,CAAC;QACxC,IAAI,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC;YAC5B,MAAM,IAAI,SAAS,CAAC,mCAAmC,OAAO,EAAE,CAAC,CAAC;QACpE,CAAC;IACH,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export declare function safeQuery(sql: string, allowedPrefixes: string[]): string;
|
|
2
|
+
export declare function runSql(sql: string, options?: {
|
|
3
|
+
database?: string;
|
|
4
|
+
}): Promise<{
|
|
5
|
+
kind: "resultset";
|
|
6
|
+
columns: string[];
|
|
7
|
+
rowCount: number;
|
|
8
|
+
totalRows: number;
|
|
9
|
+
truncated: boolean;
|
|
10
|
+
maxRows: number;
|
|
11
|
+
rows: Record<string, any>[];
|
|
12
|
+
} | {
|
|
13
|
+
kind: "execute";
|
|
14
|
+
affectedRows: number;
|
|
15
|
+
}>;
|
|
16
|
+
//# sourceMappingURL=postgres.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"postgres.d.ts","sourceRoot":"","sources":["../../src/postgres/postgres.ts"],"names":[],"mappings":"AAKA,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,EAAE,GAAG,MAAM,CAKxE;AAED,wBAAsB,MAAM,CAC1B,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,GAC9B,OAAO,CACN;IACE,IAAI,EAAE,WAAW,CAAC;IAClB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,OAAO,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,CAAC;CAC7B,GACD;IACE,IAAI,EAAE,SAAS,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;CACtB,CACJ,CA2CA"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import pg from "pg";
|
|
2
|
+
import { ToolError } from "@achmadya-dev/mcp-core";
|
|
3
|
+
import config from "./config.js";
|
|
4
|
+
import * as helpers from "./helpers.js";
|
|
5
|
+
export function safeQuery(sql, allowedPrefixes) {
|
|
6
|
+
const { cleanSql, prefixes } = helpers.validateInputs(sql, allowedPrefixes);
|
|
7
|
+
const statement = helpers.parseSingleStatement(cleanSql);
|
|
8
|
+
helpers.validateStatement(statement, prefixes);
|
|
9
|
+
return statement;
|
|
10
|
+
}
|
|
11
|
+
export async function runSql(sql, options) {
|
|
12
|
+
const database = helpers.resolveDatabase(config.database, options?.database);
|
|
13
|
+
const client = new pg.Client({
|
|
14
|
+
host: config.host,
|
|
15
|
+
port: config.port,
|
|
16
|
+
user: config.user,
|
|
17
|
+
password: config.password,
|
|
18
|
+
database,
|
|
19
|
+
});
|
|
20
|
+
try {
|
|
21
|
+
await client.connect();
|
|
22
|
+
const result = await client.query(sql);
|
|
23
|
+
// If result has fields, it's a SELECT/result set query
|
|
24
|
+
if (result.fields && result.fields.length > 0) {
|
|
25
|
+
const columns = result.fields.map((f) => f.name);
|
|
26
|
+
const all = result.rows;
|
|
27
|
+
const truncated = all.length > config.maxRows;
|
|
28
|
+
const display = all.slice(0, config.maxRows);
|
|
29
|
+
return {
|
|
30
|
+
kind: "resultset",
|
|
31
|
+
columns,
|
|
32
|
+
rowCount: display.length,
|
|
33
|
+
totalRows: all.length,
|
|
34
|
+
truncated,
|
|
35
|
+
maxRows: config.maxRows,
|
|
36
|
+
rows: display,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
// Otherwise, it's a DML/DDL execution query
|
|
40
|
+
return {
|
|
41
|
+
kind: "execute",
|
|
42
|
+
affectedRows: result.rowCount ?? 0,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
catch (e) {
|
|
46
|
+
throw new ToolError(`PostgreSQL: ${e instanceof Error ? e.message : String(e)}`);
|
|
47
|
+
}
|
|
48
|
+
finally {
|
|
49
|
+
await client.end();
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=postgres.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"postgres.js","sourceRoot":"","sources":["../../src/postgres/postgres.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACnD,OAAO,MAAM,MAAM,aAAa,CAAC;AACjC,OAAO,KAAK,OAAO,MAAM,cAAc,CAAC;AAExC,MAAM,UAAU,SAAS,CAAC,GAAW,EAAE,eAAyB;IAC9D,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,GAAG,OAAO,CAAC,cAAc,CAAC,GAAG,EAAE,eAAe,CAAC,CAAC;IAC5E,MAAM,SAAS,GAAG,OAAO,CAAC,oBAAoB,CAAC,QAAQ,CAAC,CAAC;IACzD,OAAO,CAAC,iBAAiB,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;IAC/C,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,MAAM,CAC1B,GAAW,EACX,OAA+B;IAgB/B,MAAM,QAAQ,GAAG,OAAO,CAAC,eAAe,CAAC,MAAM,CAAC,QAAQ,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC;IAE7E,MAAM,MAAM,GAAG,IAAI,EAAE,CAAC,MAAM,CAAC;QAC3B,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,QAAQ;KACT,CAAC,CAAC;IAEH,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,OAAO,EAAE,CAAC;QACvB,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAEvC,uDAAuD;QACvD,IAAI,MAAM,CAAC,MAAM,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC9C,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;YACjD,MAAM,GAAG,GAAG,MAAM,CAAC,IAA6B,CAAC;YACjD,MAAM,SAAS,GAAG,GAAG,CAAC,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC;YAC9C,MAAM,OAAO,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;YAE7C,OAAO;gBACL,IAAI,EAAE,WAAW;gBACjB,OAAO;gBACP,QAAQ,EAAE,OAAO,CAAC,MAAM;gBACxB,SAAS,EAAE,GAAG,CAAC,MAAM;gBACrB,SAAS;gBACT,OAAO,EAAE,MAAM,CAAC,OAAO;gBACvB,IAAI,EAAE,OAAO;aACd,CAAC;QACJ,CAAC;QAED,4CAA4C;QAC5C,OAAO;YACL,IAAI,EAAE,SAAS;YACf,YAAY,EAAE,MAAM,CAAC,QAAQ,IAAI,CAAC;SACnC,CAAC;IACJ,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,MAAM,IAAI,SAAS,CAAC,eAAe,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IACnF,CAAC;YAAS,CAAC;QACT,MAAM,MAAM,CAAC,GAAG,EAAE,CAAC;IACrB,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export declare const postgresQueryInputSchema: z.ZodObject<{
|
|
3
|
+
sql: z.ZodString;
|
|
4
|
+
database: z.ZodOptional<z.ZodString>;
|
|
5
|
+
}, z.core.$strip>;
|
|
6
|
+
export declare const postgresQueryOutputShape: z.ZodObject<{
|
|
7
|
+
kind: z.ZodEnum<{
|
|
8
|
+
resultset: "resultset";
|
|
9
|
+
execute: "execute";
|
|
10
|
+
}>;
|
|
11
|
+
columns: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
12
|
+
rowCount: z.ZodOptional<z.ZodNumber>;
|
|
13
|
+
totalRows: z.ZodOptional<z.ZodNumber>;
|
|
14
|
+
truncated: z.ZodOptional<z.ZodBoolean>;
|
|
15
|
+
maxRows: z.ZodOptional<z.ZodNumber>;
|
|
16
|
+
rows: z.ZodOptional<z.ZodArray<z.ZodRecord<z.ZodString, z.ZodAny>>>;
|
|
17
|
+
affectedRows: z.ZodOptional<z.ZodNumber>;
|
|
18
|
+
}, z.core.$strip>;
|
|
19
|
+
export declare const postgresQueryResultSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
20
|
+
kind: z.ZodLiteral<"resultset">;
|
|
21
|
+
columns: z.ZodArray<z.ZodString>;
|
|
22
|
+
rowCount: z.ZodNumber;
|
|
23
|
+
totalRows: z.ZodNumber;
|
|
24
|
+
truncated: z.ZodBoolean;
|
|
25
|
+
maxRows: z.ZodNumber;
|
|
26
|
+
rows: z.ZodArray<z.ZodRecord<z.ZodString, z.ZodAny>>;
|
|
27
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
28
|
+
kind: z.ZodLiteral<"execute">;
|
|
29
|
+
affectedRows: z.ZodNumber;
|
|
30
|
+
}, z.core.$strip>], "kind">;
|
|
31
|
+
export type PostgresQueryResult = z.infer<typeof postgresQueryResultSchema>;
|
|
32
|
+
//# sourceMappingURL=schema.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/postgres/schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,eAAO,MAAM,wBAAwB;;;iBAgBnC,CAAC;AAEH,eAAO,MAAM,wBAAwB;;;;;;;;;;;;iBASnC,CAAC;AAEH,eAAO,MAAM,yBAAyB;;;;;;;;;;;2BAcpC,CAAC;AAEH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export const postgresQueryInputSchema = z.object({
|
|
3
|
+
sql: z
|
|
4
|
+
.string()
|
|
5
|
+
.refine((val) => val.trim().length > 0, {
|
|
6
|
+
message: "SQL cannot be empty",
|
|
7
|
+
})
|
|
8
|
+
.refine((val) => val.length <= 100000, {
|
|
9
|
+
message: "SQL is too long (max 100,000 characters)",
|
|
10
|
+
})
|
|
11
|
+
.describe("A single SQL statement. Multiple statements separated by ';' are not allowed."),
|
|
12
|
+
database: z
|
|
13
|
+
.string()
|
|
14
|
+
.optional()
|
|
15
|
+
.describe("Optional database name for this query. Overrides POSTGRES_DATABASE from server config. Schemas within a database are accessed via schema.table in SQL."),
|
|
16
|
+
});
|
|
17
|
+
export const postgresQueryOutputShape = z.object({
|
|
18
|
+
kind: z.enum(["resultset", "execute"]),
|
|
19
|
+
columns: z.array(z.string()).optional(),
|
|
20
|
+
rowCount: z.number().int().nonnegative().optional(),
|
|
21
|
+
totalRows: z.number().int().nonnegative().optional(),
|
|
22
|
+
truncated: z.boolean().optional(),
|
|
23
|
+
maxRows: z.number().int().positive().optional(),
|
|
24
|
+
rows: z.array(z.record(z.string(), z.any())).optional(),
|
|
25
|
+
affectedRows: z.number().int().nonnegative().optional(),
|
|
26
|
+
});
|
|
27
|
+
export const postgresQueryResultSchema = z.discriminatedUnion("kind", [
|
|
28
|
+
z.object({
|
|
29
|
+
kind: z.literal("resultset"),
|
|
30
|
+
columns: z.array(z.string()),
|
|
31
|
+
rowCount: z.number().int().nonnegative(),
|
|
32
|
+
totalRows: z.number().int().nonnegative(),
|
|
33
|
+
truncated: z.boolean(),
|
|
34
|
+
maxRows: z.number().int().positive(),
|
|
35
|
+
rows: z.array(z.record(z.string(), z.any())),
|
|
36
|
+
}),
|
|
37
|
+
z.object({
|
|
38
|
+
kind: z.literal("execute"),
|
|
39
|
+
affectedRows: z.number().int().nonnegative(),
|
|
40
|
+
}),
|
|
41
|
+
]);
|
|
42
|
+
//# sourceMappingURL=schema.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/postgres/schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,MAAM,CAAC,MAAM,wBAAwB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC/C,GAAG,EAAE,CAAC;SACH,MAAM,EAAE;SACR,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,EAAE;QACtC,OAAO,EAAE,qBAAqB;KAC/B,CAAC;SACD,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,MAAM,IAAI,MAAM,EAAE;QACrC,OAAO,EAAE,0CAA0C;KACpD,CAAC;SACD,QAAQ,CAAC,+EAA+E,CAAC;IAC5F,QAAQ,EAAE,CAAC;SACR,MAAM,EAAE;SACR,QAAQ,EAAE;SACV,QAAQ,CACP,wJAAwJ,CACzJ;CACJ,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,wBAAwB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC/C,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,SAAS,CAAC,CAAC;IACtC,OAAO,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,EAAE;IACvC,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE,CAAC,QAAQ,EAAE;IACnD,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE,CAAC,QAAQ,EAAE;IACpD,SAAS,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;IACjC,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;IAC/C,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,QAAQ,EAAE;IACvD,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE,CAAC,QAAQ,EAAE;CACxD,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,yBAAyB,GAAG,CAAC,CAAC,kBAAkB,CAAC,MAAM,EAAE;IACpE,CAAC,CAAC,MAAM,CAAC;QACP,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,WAAW,CAAC;QAC5B,OAAO,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;QAC5B,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE;QACxC,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE;QACzC,SAAS,EAAE,CAAC,CAAC,OAAO,EAAE;QACtB,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE;QACpC,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC;KAC7C,CAAC;IACF,CAAC,CAAC,MAAM,CAAC;QACP,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC;QAC1B,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE;KAC7C,CAAC;CACH,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"postgres_ddl.d.ts","sourceRoot":"","sources":["../../src/tools/postgres_ddl.ts"],"names":[],"mappings":"AAUA,eAAO,MAAM,YAAY,mDA2BvB,CAAC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { defineTool, ToolError } from "@achmadya-dev/mcp-core";
|
|
2
|
+
import { runSql, safeQuery } from "../postgres/postgres.js";
|
|
3
|
+
import { postgresQueryInputSchema, postgresQueryOutputShape, postgresQueryResultSchema, } from "../postgres/schema.js";
|
|
4
|
+
import config from "../postgres/config.js";
|
|
5
|
+
export const postgres_ddl = defineTool({
|
|
6
|
+
name: "postgres_ddl",
|
|
7
|
+
description: "Modify the database schema or permissions using CREATE, ALTER, DROP, TRUNCATE, RENAME, GRANT, REVOKE, or COMMENT. Only a single query is allowed. Optionally pass database to override POSTGRES_DATABASE. If the operation is rejected as not allowed, you must respect this safety restriction and do not attempt to bypass it via terminal commands, custom scripts, or external tools.",
|
|
8
|
+
inputSchema: postgresQueryInputSchema,
|
|
9
|
+
outputSchema: postgresQueryOutputShape,
|
|
10
|
+
handler: async (input) => {
|
|
11
|
+
if (!config.allowDdl) {
|
|
12
|
+
throw new ToolError("DDL operation is not allowed on this server.");
|
|
13
|
+
}
|
|
14
|
+
const query = safeQuery(input.sql, [
|
|
15
|
+
"CREATE",
|
|
16
|
+
"ALTER",
|
|
17
|
+
"DROP",
|
|
18
|
+
"TRUNCATE",
|
|
19
|
+
"RENAME",
|
|
20
|
+
"GRANT",
|
|
21
|
+
"REVOKE",
|
|
22
|
+
"COMMENT",
|
|
23
|
+
]);
|
|
24
|
+
const result = await runSql(query, { database: input.database });
|
|
25
|
+
const parsed = postgresQueryResultSchema.safeParse(result);
|
|
26
|
+
if (!parsed.success) {
|
|
27
|
+
throw new ToolError(`Invalid query result: ${parsed.error.message}`);
|
|
28
|
+
}
|
|
29
|
+
return parsed.data;
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
//# sourceMappingURL=postgres_ddl.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"postgres_ddl.js","sourceRoot":"","sources":["../../src/tools/postgres_ddl.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAE/D,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,EACL,wBAAwB,EACxB,wBAAwB,EACxB,yBAAyB,GAC1B,MAAM,uBAAuB,CAAC;AAC/B,OAAO,MAAM,MAAM,uBAAuB,CAAC;AAE3C,MAAM,CAAC,MAAM,YAAY,GAAG,UAAU,CAAC;IACrC,IAAI,EAAE,cAAc;IACpB,WAAW,EACT,2XAA2X;IAC7X,WAAW,EAAE,wBAAwB;IACrC,YAAY,EAAE,wBAAwB;IACtC,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE;QACvB,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC;YACrB,MAAM,IAAI,SAAS,CAAC,8CAA8C,CAAC,CAAC;QACtE,CAAC;QACD,MAAM,KAAK,GAAG,SAAS,CAAC,KAAK,CAAC,GAAG,EAAE;YACjC,QAAQ;YACR,OAAO;YACP,MAAM;YACN,UAAU;YACV,QAAQ;YACR,OAAO;YACP,QAAQ;YACR,SAAS;SACV,CAAC,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,KAAK,EAAE,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC;QACjE,MAAM,MAAM,GAAG,yBAAyB,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;QAC3D,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YACpB,MAAM,IAAI,SAAS,CAAC,yBAAyB,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;QACvE,CAAC;QACD,OAAO,MAAM,CAAC,IAAI,CAAC;IACrB,CAAC;CACF,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"postgres_delete.d.ts","sourceRoot":"","sources":["../../src/tools/postgres_delete.ts"],"names":[],"mappings":"AAUA,eAAO,MAAM,eAAe,mDAkB1B,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { defineTool, ToolError } from "@achmadya-dev/mcp-core";
|
|
2
|
+
import { runSql, safeQuery } from "../postgres/postgres.js";
|
|
3
|
+
import { postgresQueryInputSchema, postgresQueryOutputShape, postgresQueryResultSchema, } from "../postgres/schema.js";
|
|
4
|
+
import config from "../postgres/config.js";
|
|
5
|
+
export const postgres_delete = defineTool({
|
|
6
|
+
name: "postgres_delete",
|
|
7
|
+
description: "Delete data from the database using DELETE. Only a single query is allowed. Optionally pass database to override POSTGRES_DATABASE. If the operation is rejected as not allowed, you must respect this safety restriction and do not attempt to bypass it via terminal commands, custom scripts, or external tools.",
|
|
8
|
+
inputSchema: postgresQueryInputSchema,
|
|
9
|
+
outputSchema: postgresQueryOutputShape,
|
|
10
|
+
handler: async (input) => {
|
|
11
|
+
if (!config.allowDelete) {
|
|
12
|
+
throw new ToolError("DELETE operation is not allowed on this server.");
|
|
13
|
+
}
|
|
14
|
+
const query = safeQuery(input.sql, ["DELETE"]);
|
|
15
|
+
const result = await runSql(query, { database: input.database });
|
|
16
|
+
const parsed = postgresQueryResultSchema.safeParse(result);
|
|
17
|
+
if (!parsed.success) {
|
|
18
|
+
throw new ToolError(`Invalid query result: ${parsed.error.message}`);
|
|
19
|
+
}
|
|
20
|
+
return parsed.data;
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
//# sourceMappingURL=postgres_delete.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"postgres_delete.js","sourceRoot":"","sources":["../../src/tools/postgres_delete.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAE/D,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,EACL,wBAAwB,EACxB,wBAAwB,EACxB,yBAAyB,GAC1B,MAAM,uBAAuB,CAAC;AAC/B,OAAO,MAAM,MAAM,uBAAuB,CAAC;AAE3C,MAAM,CAAC,MAAM,eAAe,GAAG,UAAU,CAAC;IACxC,IAAI,EAAE,iBAAiB;IACvB,WAAW,EACT,qTAAqT;IACvT,WAAW,EAAE,wBAAwB;IACrC,YAAY,EAAE,wBAAwB;IACtC,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE;QACvB,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC;YACxB,MAAM,IAAI,SAAS,CAAC,iDAAiD,CAAC,CAAC;QACzE,CAAC;QACD,MAAM,KAAK,GAAG,SAAS,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC;QAC/C,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,KAAK,EAAE,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC;QACjE,MAAM,MAAM,GAAG,yBAAyB,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;QAC3D,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YACpB,MAAM,IAAI,SAAS,CAAC,yBAAyB,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;QACvE,CAAC;QACD,OAAO,MAAM,CAAC,IAAI,CAAC;IACrB,CAAC;CACF,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"postgres_insert.d.ts","sourceRoot":"","sources":["../../src/tools/postgres_insert.ts"],"names":[],"mappings":"AAUA,eAAO,MAAM,eAAe,mDAkB1B,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { defineTool, ToolError } from "@achmadya-dev/mcp-core";
|
|
2
|
+
import { runSql, safeQuery } from "../postgres/postgres.js";
|
|
3
|
+
import { postgresQueryInputSchema, postgresQueryOutputShape, postgresQueryResultSchema, } from "../postgres/schema.js";
|
|
4
|
+
import config from "../postgres/config.js";
|
|
5
|
+
export const postgres_insert = defineTool({
|
|
6
|
+
name: "postgres_insert",
|
|
7
|
+
description: "Insert new data into the database using INSERT. Only a single query is allowed. Optionally pass database to override POSTGRES_DATABASE. If the operation is rejected as not allowed, you must respect this safety restriction and do not attempt to bypass it via terminal commands, custom scripts, or external tools.",
|
|
8
|
+
inputSchema: postgresQueryInputSchema,
|
|
9
|
+
outputSchema: postgresQueryOutputShape,
|
|
10
|
+
handler: async (input) => {
|
|
11
|
+
if (!config.allowInsert) {
|
|
12
|
+
throw new ToolError("INSERT operation is not allowed on this server.");
|
|
13
|
+
}
|
|
14
|
+
const query = safeQuery(input.sql, ["INSERT"]);
|
|
15
|
+
const result = await runSql(query, { database: input.database });
|
|
16
|
+
const parsed = postgresQueryResultSchema.safeParse(result);
|
|
17
|
+
if (!parsed.success) {
|
|
18
|
+
throw new ToolError(`Invalid query result: ${parsed.error.message}`);
|
|
19
|
+
}
|
|
20
|
+
return parsed.data;
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
//# sourceMappingURL=postgres_insert.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"postgres_insert.js","sourceRoot":"","sources":["../../src/tools/postgres_insert.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAE/D,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,EACL,wBAAwB,EACxB,wBAAwB,EACxB,yBAAyB,GAC1B,MAAM,uBAAuB,CAAC;AAC/B,OAAO,MAAM,MAAM,uBAAuB,CAAC;AAE3C,MAAM,CAAC,MAAM,eAAe,GAAG,UAAU,CAAC;IACxC,IAAI,EAAE,iBAAiB;IACvB,WAAW,EACT,yTAAyT;IAC3T,WAAW,EAAE,wBAAwB;IACrC,YAAY,EAAE,wBAAwB;IACtC,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE;QACvB,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC;YACxB,MAAM,IAAI,SAAS,CAAC,iDAAiD,CAAC,CAAC;QACzE,CAAC;QACD,MAAM,KAAK,GAAG,SAAS,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC;QAC/C,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,KAAK,EAAE,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC;QACjE,MAAM,MAAM,GAAG,yBAAyB,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;QAC3D,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YACpB,MAAM,IAAI,SAAS,CAAC,yBAAyB,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;QACvE,CAAC;QACD,OAAO,MAAM,CAAC,IAAI,CAAC;IACrB,CAAC;CACF,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"postgres_select.d.ts","sourceRoot":"","sources":["../../src/tools/postgres_select.ts"],"names":[],"mappings":"AASA,eAAO,MAAM,eAAe,mDAe1B,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { defineTool, ToolError } from "@achmadya-dev/mcp-core";
|
|
2
|
+
import { runSql, safeQuery } from "../postgres/postgres.js";
|
|
3
|
+
import { postgresQueryInputSchema, postgresQueryOutputShape, postgresQueryResultSchema, } from "../postgres/schema.js";
|
|
4
|
+
export const postgres_select = defineTool({
|
|
5
|
+
name: "postgres_select",
|
|
6
|
+
description: "Read data from the database using SELECT, EXPLAIN, TABLE, VALUES, or SHOW. Only a single query is allowed. Optionally pass database to override POSTGRES_DATABASE. Use z.table in SQL for other schemas within the same database.",
|
|
7
|
+
inputSchema: postgresQueryInputSchema,
|
|
8
|
+
outputSchema: postgresQueryOutputShape,
|
|
9
|
+
handler: async (input) => {
|
|
10
|
+
const query = safeQuery(input.sql, ["SELECT", "EXPLAIN", "TABLE", "VALUES", "SHOW"]);
|
|
11
|
+
const result = await runSql(query, { database: input.database });
|
|
12
|
+
const parsed = postgresQueryResultSchema.safeParse(result);
|
|
13
|
+
if (!parsed.success) {
|
|
14
|
+
throw new ToolError(`Invalid query result: ${parsed.error.message}`);
|
|
15
|
+
}
|
|
16
|
+
return parsed.data;
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
//# sourceMappingURL=postgres_select.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"postgres_select.js","sourceRoot":"","sources":["../../src/tools/postgres_select.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAE/D,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,EACL,wBAAwB,EACxB,wBAAwB,EACxB,yBAAyB,GAC1B,MAAM,uBAAuB,CAAC;AAE/B,MAAM,CAAC,MAAM,eAAe,GAAG,UAAU,CAAC;IACxC,IAAI,EAAE,iBAAiB;IACvB,WAAW,EACT,mOAAmO;IACrO,WAAW,EAAE,wBAAwB;IACrC,YAAY,EAAE,wBAAwB;IACtC,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE;QACvB,MAAM,KAAK,GAAG,SAAS,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,SAAS,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC;QACrF,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,KAAK,EAAE,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC;QACjE,MAAM,MAAM,GAAG,yBAAyB,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;QAC3D,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YACpB,MAAM,IAAI,SAAS,CAAC,yBAAyB,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;QACvE,CAAC;QACD,OAAO,MAAM,CAAC,IAAI,CAAC;IACrB,CAAC;CACF,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"postgres_update.d.ts","sourceRoot":"","sources":["../../src/tools/postgres_update.ts"],"names":[],"mappings":"AAUA,eAAO,MAAM,eAAe,mDAkB1B,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { defineTool, ToolError } from "@achmadya-dev/mcp-core";
|
|
2
|
+
import { runSql, safeQuery } from "../postgres/postgres.js";
|
|
3
|
+
import { postgresQueryInputSchema, postgresQueryOutputShape, postgresQueryResultSchema, } from "../postgres/schema.js";
|
|
4
|
+
import config from "../postgres/config.js";
|
|
5
|
+
export const postgres_update = defineTool({
|
|
6
|
+
name: "postgres_update",
|
|
7
|
+
description: "Update existing data in the database using UPDATE. Only a single query is allowed. Optionally pass database to override POSTGRES_DATABASE. If the operation is rejected as not allowed, you must respect this safety restriction and do not attempt to bypass it via terminal commands, custom scripts, or external tools.",
|
|
8
|
+
inputSchema: postgresQueryInputSchema,
|
|
9
|
+
outputSchema: postgresQueryOutputShape,
|
|
10
|
+
handler: async (input) => {
|
|
11
|
+
if (!config.allowUpdate) {
|
|
12
|
+
throw new ToolError("UPDATE operation is not allowed on this server.");
|
|
13
|
+
}
|
|
14
|
+
const query = safeQuery(input.sql, ["UPDATE"]);
|
|
15
|
+
const result = await runSql(query, { database: input.database });
|
|
16
|
+
const parsed = postgresQueryResultSchema.safeParse(result);
|
|
17
|
+
if (!parsed.success) {
|
|
18
|
+
throw new ToolError(`Invalid query result: ${parsed.error.message}`);
|
|
19
|
+
}
|
|
20
|
+
return parsed.data;
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
//# sourceMappingURL=postgres_update.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"postgres_update.js","sourceRoot":"","sources":["../../src/tools/postgres_update.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAE/D,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,EACL,wBAAwB,EACxB,wBAAwB,EACxB,yBAAyB,GAC1B,MAAM,uBAAuB,CAAC;AAC/B,OAAO,MAAM,MAAM,uBAAuB,CAAC;AAE3C,MAAM,CAAC,MAAM,eAAe,GAAG,UAAU,CAAC;IACxC,IAAI,EAAE,iBAAiB;IACvB,WAAW,EACT,4TAA4T;IAC9T,WAAW,EAAE,wBAAwB;IACrC,YAAY,EAAE,wBAAwB;IACtC,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE;QACvB,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC;YACxB,MAAM,IAAI,SAAS,CAAC,iDAAiD,CAAC,CAAC;QACzE,CAAC;QACD,MAAM,KAAK,GAAG,SAAS,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC;QAC/C,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,KAAK,EAAE,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC;QACjE,MAAM,MAAM,GAAG,yBAAyB,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;QAC3D,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YACpB,MAAM,IAAI,SAAS,CAAC,yBAAyB,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;QACvE,CAAC;QACD,OAAO,MAAM,CAAC,IAAI,CAAC;IACrB,CAAC;CACF,CAAC,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@achmadya-dev/mcp-postgres-query",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Model Context Protocol (MCP) server for PostgreSQL to run SQL queries via stdio (read-only by default)",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"author": "achmadya",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/achmadya-dev/mcp-postgres-query.git"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"mcp",
|
|
14
|
+
"model-context-protocol",
|
|
15
|
+
"postgres",
|
|
16
|
+
"postgresql",
|
|
17
|
+
"database",
|
|
18
|
+
"cursor"
|
|
19
|
+
],
|
|
20
|
+
"main": "dist/index.js",
|
|
21
|
+
"bin": {
|
|
22
|
+
"mcp-postgres-query": "./dist/index.js"
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
"dist"
|
|
26
|
+
],
|
|
27
|
+
"publishConfig": {
|
|
28
|
+
"access": "public"
|
|
29
|
+
},
|
|
30
|
+
"engines": {
|
|
31
|
+
"node": ">=20"
|
|
32
|
+
},
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"@achmadya-dev/mcp-core": "^0.3.3",
|
|
35
|
+
"pg": "8.13.1",
|
|
36
|
+
"zod": "4.3.6"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@jest/globals": "30.4.1",
|
|
40
|
+
"@types/jest": "30.0.0",
|
|
41
|
+
"@types/node": "22.10.0",
|
|
42
|
+
"@types/pg": "8.11.10",
|
|
43
|
+
"jest": "30.4.2",
|
|
44
|
+
"ts-jest": "29.4.11",
|
|
45
|
+
"typescript": "5.7.2",
|
|
46
|
+
"@changesets/cli": "2.29.8"
|
|
47
|
+
},
|
|
48
|
+
"scripts": {
|
|
49
|
+
"build": "tsc -p tsconfig.build.json",
|
|
50
|
+
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
|
|
51
|
+
"start": "node dist/index.js",
|
|
52
|
+
"changeset": "changeset",
|
|
53
|
+
"version-packages": "changeset version",
|
|
54
|
+
"publish-packages": "pnpm run build && changeset publish"
|
|
55
|
+
}
|
|
56
|
+
}
|