pg-admin-mcp 0.0.0-stage → 0.1.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 +608 -2
- package/dist/config/env.js +67 -0
- package/dist/config/env.js.map +1 -0
- package/dist/config/profiles.js +257 -0
- package/dist/config/profiles.js.map +1 -0
- package/dist/core/audit-context.js +21 -0
- package/dist/core/audit-context.js.map +1 -0
- package/dist/core/audit.js +91 -0
- package/dist/core/audit.js.map +1 -0
- package/dist/core/brands.js +24 -0
- package/dist/core/brands.js.map +1 -0
- package/dist/core/classifier.js +279 -0
- package/dist/core/classifier.js.map +1 -0
- package/dist/core/errors.js +197 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/core/format.js +258 -0
- package/dist/core/format.js.map +1 -0
- package/dist/core/guards.js +513 -0
- package/dist/core/guards.js.map +1 -0
- package/dist/core/identifiers.js +431 -0
- package/dist/core/identifiers.js.map +1 -0
- package/dist/core/limits.js +79 -0
- package/dist/core/limits.js.map +1 -0
- package/dist/core/logger.js +34 -0
- package/dist/core/logger.js.map +1 -0
- package/dist/core/paginate.js +195 -0
- package/dist/core/paginate.js.map +1 -0
- package/dist/core/paths.js +392 -0
- package/dist/core/paths.js.map +1 -0
- package/dist/core/policy.js +98 -0
- package/dist/core/policy.js.map +1 -0
- package/dist/core/shutdown.js +10 -0
- package/dist/core/shutdown.js.map +1 -0
- package/dist/core/stdout-guard.js +25 -0
- package/dist/core/stdout-guard.js.map +1 -0
- package/dist/db/catalog.js +1006 -0
- package/dist/db/catalog.js.map +1 -0
- package/dist/db/pool.js +158 -0
- package/dist/db/pool.js.map +1 -0
- package/dist/db/session.js +258 -0
- package/dist/db/session.js.map +1 -0
- package/dist/db/types.js +2 -0
- package/dist/db/types.js.map +1 -0
- package/dist/index.js +91 -0
- package/dist/index.js.map +1 -0
- package/dist/resources/index.js +88 -0
- package/dist/resources/index.js.map +1 -0
- package/dist/runtime/binaries.js +356 -0
- package/dist/runtime/binaries.js.map +1 -0
- package/dist/runtime/env-file.js +62 -0
- package/dist/runtime/env-file.js.map +1 -0
- package/dist/runtime/environment.js +34 -0
- package/dist/runtime/environment.js.map +1 -0
- package/dist/server.js +106 -0
- package/dist/server.js.map +1 -0
- package/dist/snapshot/capture.js +189 -0
- package/dist/snapshot/capture.js.map +1 -0
- package/dist/snapshot/diff.js +254 -0
- package/dist/snapshot/diff.js.map +1 -0
- package/dist/snapshot/migrate.js +1596 -0
- package/dist/snapshot/migrate.js.map +1 -0
- package/dist/snapshot/model.js +80 -0
- package/dist/snapshot/model.js.map +1 -0
- package/dist/tools/_archive_meta.js +138 -0
- package/dist/tools/_archive_meta.js.map +1 -0
- package/dist/tools/_batch_shared.js +73 -0
- package/dist/tools/_batch_shared.js.map +1 -0
- package/dist/tools/_export_format.js +107 -0
- package/dist/tools/_export_format.js.map +1 -0
- package/dist/tools/_object_kinds.js +97 -0
- package/dist/tools/_object_kinds.js.map +1 -0
- package/dist/tools/_relation_detail.js +55 -0
- package/dist/tools/_relation_detail.js.map +1 -0
- package/dist/tools/_shared.js +17 -0
- package/dist/tools/_shared.js.map +1 -0
- package/dist/tools/_snapshot_io.js +238 -0
- package/dist/tools/_snapshot_io.js.map +1 -0
- package/dist/tools/admin/_shared.js +18 -0
- package/dist/tools/admin/_shared.js.map +1 -0
- package/dist/tools/admin/extensions.js +80 -0
- package/dist/tools/admin/extensions.js.map +1 -0
- package/dist/tools/admin/grants.js +207 -0
- package/dist/tools/admin/grants.js.map +1 -0
- package/dist/tools/admin/index.js +138 -0
- package/dist/tools/admin/index.js.map +1 -0
- package/dist/tools/admin/maintenance.js +106 -0
- package/dist/tools/admin/maintenance.js.map +1 -0
- package/dist/tools/admin/roles.js +144 -0
- package/dist/tools/admin/roles.js.map +1 -0
- package/dist/tools/admin/sessions.js +104 -0
- package/dist/tools/admin/sessions.js.map +1 -0
- package/dist/tools/admin/stats.js +63 -0
- package/dist/tools/admin/stats.js.map +1 -0
- package/dist/tools/backup.js +288 -0
- package/dist/tools/backup.js.map +1 -0
- package/dist/tools/connection_status.js +138 -0
- package/dist/tools/connection_status.js.map +1 -0
- package/dist/tools/copy.js +355 -0
- package/dist/tools/copy.js.map +1 -0
- package/dist/tools/delete_batch.js +73 -0
- package/dist/tools/delete_batch.js.map +1 -0
- package/dist/tools/describe_object.js +87 -0
- package/dist/tools/describe_object.js.map +1 -0
- package/dist/tools/execute_sql.js +395 -0
- package/dist/tools/execute_sql.js.map +1 -0
- package/dist/tools/explain.js +134 -0
- package/dist/tools/explain.js.map +1 -0
- package/dist/tools/export.js +215 -0
- package/dist/tools/export.js.map +1 -0
- package/dist/tools/get_schema.js +148 -0
- package/dist/tools/get_schema.js.map +1 -0
- package/dist/tools/index.js +40 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/insert_batch.js +212 -0
- package/dist/tools/insert_batch.js.map +1 -0
- package/dist/tools/list_objects.js +160 -0
- package/dist/tools/list_objects.js.map +1 -0
- package/dist/tools/prepare_migration.js +112 -0
- package/dist/tools/prepare_migration.js.map +1 -0
- package/dist/tools/restore.js +301 -0
- package/dist/tools/restore.js.map +1 -0
- package/dist/tools/schema_diff.js +79 -0
- package/dist/tools/schema_diff.js.map +1 -0
- package/dist/tools/schema_snapshot.js +57 -0
- package/dist/tools/schema_snapshot.js.map +1 -0
- package/dist/tools/select.js +162 -0
- package/dist/tools/select.js.map +1 -0
- package/dist/tools/update_batch.js +154 -0
- package/dist/tools/update_batch.js.map +1 -0
- package/package.json +47 -5
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
import { PgMcpError } from './errors.js';
|
|
2
|
+
/**
|
|
3
|
+
* PostgreSQL treats every non-ASCII byte as an identifier/tag character. The
|
|
4
|
+
* ranges are spelled as a codepoint test rather than a regex range so this file
|
|
5
|
+
* stays pure ASCII — an invisible character in a security-relevant scanner is
|
|
6
|
+
* exactly the kind of thing nobody spots in review.
|
|
7
|
+
*
|
|
8
|
+
* Failing to recognize a tag would make the scanner *over*-split on `;`, which
|
|
9
|
+
* fails closed (the call is refused), never open.
|
|
10
|
+
*/
|
|
11
|
+
function isHigh(ch) {
|
|
12
|
+
return ch.charCodeAt(0) > 127;
|
|
13
|
+
}
|
|
14
|
+
function isTagStart(ch) {
|
|
15
|
+
return /[A-Za-z_]/.test(ch) || isHigh(ch);
|
|
16
|
+
}
|
|
17
|
+
function isTagChar(ch) {
|
|
18
|
+
return /[A-Za-z0-9_]/.test(ch) || isHigh(ch);
|
|
19
|
+
}
|
|
20
|
+
/** `$` is an identifier character in PostgreSQL, which is why `a$$b$$` is not a dollar quote. */
|
|
21
|
+
function isIdentChar(ch) {
|
|
22
|
+
return /[A-Za-z0-9_$]/.test(ch) || isHigh(ch);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* End index (exclusive) of a `'…'` or `"…"` run. A doubled quote continues it.
|
|
26
|
+
* `escapes` is only ever true for an `E'…'` string — in a plain string under
|
|
27
|
+
* `standard_conforming_strings = on` a backslash is an ordinary character, and
|
|
28
|
+
* treating `\'` as an escape there would swallow the real terminator and, with
|
|
29
|
+
* it, any `;` that followed. Unterminated runs stop at end of input; PostgreSQL
|
|
30
|
+
* gets to reject those itself.
|
|
31
|
+
*/
|
|
32
|
+
function scanQuoted(sql, start, quote, escapes) {
|
|
33
|
+
for (let i = start + 1; i < sql.length; i++) {
|
|
34
|
+
const ch = sql.charAt(i);
|
|
35
|
+
if (escapes && ch === '\\') {
|
|
36
|
+
i++;
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
if (ch !== quote)
|
|
40
|
+
continue;
|
|
41
|
+
if (sql.charAt(i + 1) === quote) {
|
|
42
|
+
i++;
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
return i + 1;
|
|
46
|
+
}
|
|
47
|
+
return sql.length;
|
|
48
|
+
}
|
|
49
|
+
/** True when the quote at `i` opens an `E'…'` escape string. */
|
|
50
|
+
function hasEscapePrefix(sql, i) {
|
|
51
|
+
const prev = sql.charAt(i - 1);
|
|
52
|
+
if (prev !== 'E' && prev !== 'e')
|
|
53
|
+
return false;
|
|
54
|
+
return i < 2 || !isIdentChar(sql.charAt(i - 2));
|
|
55
|
+
}
|
|
56
|
+
/** PostgreSQL block comments nest, so this counts depth instead of stopping at the first close. */
|
|
57
|
+
function scanBlockComment(sql, start) {
|
|
58
|
+
let depth = 1;
|
|
59
|
+
let i = start + 2;
|
|
60
|
+
while (i < sql.length) {
|
|
61
|
+
if (sql.charAt(i) === '/' && sql.charAt(i + 1) === '*') {
|
|
62
|
+
depth++;
|
|
63
|
+
i += 2;
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
if (sql.charAt(i) === '*' && sql.charAt(i + 1) === '/') {
|
|
67
|
+
depth--;
|
|
68
|
+
i += 2;
|
|
69
|
+
if (depth === 0)
|
|
70
|
+
return i;
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
i++;
|
|
74
|
+
}
|
|
75
|
+
return sql.length;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* End index of a `$tag$ … $tag$` body, or `start` when the `$` is not a
|
|
79
|
+
* dollar-quote opener at all (`$1` placeholders, `a$b` identifiers). Only an
|
|
80
|
+
* exact repeat of the opening tag closes the body — which is precisely why a
|
|
81
|
+
* `$inner$` sequence inside a `$outer$` body is inert text and needs no nesting
|
|
82
|
+
* logic of its own.
|
|
83
|
+
*/
|
|
84
|
+
function scanDollarQuoted(sql, start) {
|
|
85
|
+
if (start > 0 && isIdentChar(sql.charAt(start - 1)))
|
|
86
|
+
return start;
|
|
87
|
+
let i = start + 1;
|
|
88
|
+
if (i < sql.length && isTagStart(sql.charAt(i))) {
|
|
89
|
+
i++;
|
|
90
|
+
while (i < sql.length && isTagChar(sql.charAt(i)))
|
|
91
|
+
i++;
|
|
92
|
+
}
|
|
93
|
+
if (sql.charAt(i) !== '$')
|
|
94
|
+
return start;
|
|
95
|
+
const tag = sql.slice(start, i + 1);
|
|
96
|
+
const close = sql.indexOf(tag, i + 1);
|
|
97
|
+
return close === -1 ? sql.length : close + tag.length;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* The one scanner. Both exported functions consume it, so they cannot drift
|
|
101
|
+
* into disagreeing about where a string or a comment ends.
|
|
102
|
+
*/
|
|
103
|
+
function segments(sql) {
|
|
104
|
+
const out = [];
|
|
105
|
+
let i = 0;
|
|
106
|
+
let codeStart = 0;
|
|
107
|
+
const flushCode = (upTo) => {
|
|
108
|
+
if (upTo > codeStart)
|
|
109
|
+
out.push({ kind: 'code', start: codeStart, end: upTo });
|
|
110
|
+
};
|
|
111
|
+
const push = (kind, end) => {
|
|
112
|
+
flushCode(i);
|
|
113
|
+
out.push({ kind, start: i, end });
|
|
114
|
+
i = end;
|
|
115
|
+
codeStart = end;
|
|
116
|
+
};
|
|
117
|
+
while (i < sql.length) {
|
|
118
|
+
const ch = sql.charAt(i);
|
|
119
|
+
const next = sql.charAt(i + 1);
|
|
120
|
+
if (ch === "'") {
|
|
121
|
+
push('literal', scanQuoted(sql, i, "'", hasEscapePrefix(sql, i)));
|
|
122
|
+
}
|
|
123
|
+
else if (ch === '"') {
|
|
124
|
+
push('literal', scanQuoted(sql, i, '"', false));
|
|
125
|
+
}
|
|
126
|
+
else if (ch === '-' && next === '-') {
|
|
127
|
+
const nl = sql.indexOf('\n', i);
|
|
128
|
+
push('comment', nl === -1 ? sql.length : nl);
|
|
129
|
+
}
|
|
130
|
+
else if (ch === '/' && next === '*') {
|
|
131
|
+
push('comment', scanBlockComment(sql, i));
|
|
132
|
+
}
|
|
133
|
+
else if (ch === ';') {
|
|
134
|
+
push('terminator', i + 1);
|
|
135
|
+
}
|
|
136
|
+
else if (ch === '$') {
|
|
137
|
+
const end = scanDollarQuoted(sql, i);
|
|
138
|
+
if (end > i)
|
|
139
|
+
push('literal', end);
|
|
140
|
+
else
|
|
141
|
+
i++; // `$1` and friends stay part of the surrounding code segment
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
i++;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
flushCode(sql.length);
|
|
148
|
+
return out;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* The raw segmentation, for consumers that need to look at SQL *text* without
|
|
152
|
+
* being fooled by a keyword hiding in a string literal or a comment —
|
|
153
|
+
* `core/guards.ts` uses it for both the WHERE guard and `normalizeSql`.
|
|
154
|
+
*
|
|
155
|
+
* Exported as a thin pass-through on purpose: `segments` itself stays private
|
|
156
|
+
* so the scanner has exactly one implementation, and everything that needs to
|
|
157
|
+
* reason about literal-vs-code boundaries agrees with `splitStatements` by
|
|
158
|
+
* construction.
|
|
159
|
+
*/
|
|
160
|
+
export function scanSegments(sql) {
|
|
161
|
+
return segments(sql);
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Splits `sql` into executable statements, with the `;` terminators stripped.
|
|
165
|
+
* Whitespace-only and comment-only runs yield nothing — a trailing `;`, a
|
|
166
|
+
* trailing `-- note`, and `''` all produce zero extra statements rather than a
|
|
167
|
+
* phantom empty one.
|
|
168
|
+
*/
|
|
169
|
+
export function splitStatements(sql) {
|
|
170
|
+
const out = [];
|
|
171
|
+
let start = 0;
|
|
172
|
+
let significant = false;
|
|
173
|
+
for (const seg of segments(sql)) {
|
|
174
|
+
if (seg.kind === 'terminator') {
|
|
175
|
+
if (significant)
|
|
176
|
+
out.push(sql.slice(start, seg.start).trim());
|
|
177
|
+
start = seg.end;
|
|
178
|
+
significant = false;
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
if (seg.kind === 'literal')
|
|
182
|
+
significant = true;
|
|
183
|
+
else if (seg.kind === 'code' && sql.slice(seg.start, seg.end).trim() !== '')
|
|
184
|
+
significant = true;
|
|
185
|
+
}
|
|
186
|
+
if (significant)
|
|
187
|
+
out.push(sql.slice(start).trim());
|
|
188
|
+
return out;
|
|
189
|
+
}
|
|
190
|
+
/** Statements that write, recognized by their leading keyword only. See the file header for why it stops there. */
|
|
191
|
+
const WRITE_KEYWORDS = new Set([
|
|
192
|
+
'INSERT',
|
|
193
|
+
'UPDATE',
|
|
194
|
+
'DELETE',
|
|
195
|
+
'MERGE',
|
|
196
|
+
'DROP',
|
|
197
|
+
'CREATE',
|
|
198
|
+
'ALTER',
|
|
199
|
+
'TRUNCATE',
|
|
200
|
+
'GRANT',
|
|
201
|
+
'REVOKE',
|
|
202
|
+
]);
|
|
203
|
+
/**
|
|
204
|
+
* The statement's leading keyword, upper-cased — or `undefined` when the
|
|
205
|
+
* statement opens with something that is not a bare word at all (a string
|
|
206
|
+
* literal, `(`-wrapped nothing, an empty statement).
|
|
207
|
+
*
|
|
208
|
+
* Leading comments are skipped (a commented preamble in front of an INSERT is
|
|
209
|
+
* still an INSERT), and a keyword that only appears inside a string literal or
|
|
210
|
+
* a quoted identifier is invisible here — the scanner never hands those back as
|
|
211
|
+
* code.
|
|
212
|
+
*
|
|
213
|
+
* **Only ever the FIRST word.** It answers "what kind of statement does this
|
|
214
|
+
* look like", never "what does this statement do": `WITH x AS (INSERT …) SELECT`
|
|
215
|
+
* reads as `WITH`, and that is deliberate — see {@link obviousWriteKeyword}.
|
|
216
|
+
*/
|
|
217
|
+
export function leadingKeyword(statement) {
|
|
218
|
+
for (const seg of segments(statement)) {
|
|
219
|
+
if (seg.kind === 'comment' || seg.kind === 'terminator')
|
|
220
|
+
continue;
|
|
221
|
+
if (seg.kind === 'literal')
|
|
222
|
+
return undefined; // a statement that opens with a literal is not keyword-led
|
|
223
|
+
const word = /^[\s(]*([A-Za-z_]+)/.exec(statement.slice(seg.start, seg.end));
|
|
224
|
+
if (word === null)
|
|
225
|
+
continue; // whitespace-only code run, keep looking
|
|
226
|
+
return (word[1] ?? '').toUpperCase();
|
|
227
|
+
}
|
|
228
|
+
return undefined;
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* The statement's leading keyword when it is an obvious writer, else
|
|
232
|
+
* `undefined`.
|
|
233
|
+
*
|
|
234
|
+
* Returns `undefined` for `WITH x AS (INSERT …) SELECT …` **on purpose**: that
|
|
235
|
+
* statement is rejected by the read-only transaction with SQLSTATE 25006, which
|
|
236
|
+
* is the actual mechanism, and a pre-check that appeared to cover it would
|
|
237
|
+
* invite the next reader to trust this function as the fence.
|
|
238
|
+
*/
|
|
239
|
+
export function obviousWriteKeyword(statement) {
|
|
240
|
+
const keyword = leadingKeyword(statement);
|
|
241
|
+
return keyword !== undefined && WRITE_KEYWORDS.has(keyword) ? keyword : undefined;
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Splits `sql` and enforces the multi-statement policy of design.md risk 7.
|
|
245
|
+
*
|
|
246
|
+
* The `hasParams` case is called out separately because the failure mode it
|
|
247
|
+
* prevents is silent: `pg` would fall back to simple-query mode and **drop the
|
|
248
|
+
* parameters on the floor**, running whatever the SQL text happened to say.
|
|
249
|
+
*/
|
|
250
|
+
export function planStatements(sql, policy) {
|
|
251
|
+
const statements = splitStatements(sql);
|
|
252
|
+
if (statements.length === 0) {
|
|
253
|
+
throw new PgMcpError({
|
|
254
|
+
code: 'PG_ERROR',
|
|
255
|
+
message: 'sql contains no executable statement (it is empty, whitespace, or only comments)',
|
|
256
|
+
hint: 'pass one SQL statement in sql and re-run',
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
if (statements.length > 1 && policy.hasParams) {
|
|
260
|
+
throw new PgMcpError({
|
|
261
|
+
code: 'PG_ERROR',
|
|
262
|
+
message: `sql contains ${statements.length} statements and params were supplied — PostgreSQL only binds parameters in ` +
|
|
263
|
+
'extended query mode, which accepts exactly one statement, so a multi-statement string would otherwise run ' +
|
|
264
|
+
'with the parameters silently dropped',
|
|
265
|
+
hint: 'split this into one statement per call, each with its own params, and re-run',
|
|
266
|
+
details: { statements: statements.length },
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
if (statements.length > 1 && !policy.allowMultiple) {
|
|
270
|
+
throw new PgMcpError({
|
|
271
|
+
code: 'PG_ERROR',
|
|
272
|
+
message: `sql contains ${statements.length} statements, and multi-statement strings are refused by default`,
|
|
273
|
+
hint: 'send one statement per call, or pass allowMultiple: true if running all of them really is intended',
|
|
274
|
+
details: { statements: statements.length },
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
return statements;
|
|
278
|
+
}
|
|
279
|
+
//# sourceMappingURL=classifier.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"classifier.js","sourceRoot":"","sources":["../../src/core/classifier.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { inContainer } from '../runtime/environment.js';
|
|
2
|
+
export class PgMcpError extends Error {
|
|
3
|
+
code;
|
|
4
|
+
hint;
|
|
5
|
+
details;
|
|
6
|
+
constructor(init) {
|
|
7
|
+
if (init.hint.trim() === '') {
|
|
8
|
+
throw new Error('PgMcpError requires a non-empty hint — an actionless error cannot be constructed.');
|
|
9
|
+
}
|
|
10
|
+
super(`${init.message} → ${init.hint}`, init.cause === undefined ? undefined : { cause: init.cause });
|
|
11
|
+
// No manual prototype patch needed: at `target: ES2023`, `class ... extends
|
|
12
|
+
// Error` compiles to a native class, and `super()` already sets the
|
|
13
|
+
// prototype from `new.target` — including for subclasses.
|
|
14
|
+
this.name = 'PgMcpError';
|
|
15
|
+
this.code = init.code;
|
|
16
|
+
this.hint = init.hint;
|
|
17
|
+
this.details = init.details;
|
|
18
|
+
}
|
|
19
|
+
toString() {
|
|
20
|
+
return `${this.code}: ${this.message}`;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* `dev` -> `PG_DEV`; unknown profile -> `PG_<ALIAS>` so the hint still reads as
|
|
25
|
+
* a template. Exported so `config/profiles.ts` derives the exact same
|
|
26
|
+
* per-profile env prefix from an alias — one definition, so the two cannot
|
|
27
|
+
* drift apart.
|
|
28
|
+
*/
|
|
29
|
+
export function envPrefix(profile) {
|
|
30
|
+
if (profile === undefined || profile === '')
|
|
31
|
+
return 'PG_<ALIAS>';
|
|
32
|
+
return `PG_${profile.toUpperCase().replace(/[^A-Z0-9]/g, '_')}`;
|
|
33
|
+
}
|
|
34
|
+
function errCode(err) {
|
|
35
|
+
if (typeof err === 'object' && err !== null && 'code' in err) {
|
|
36
|
+
const code = err.code;
|
|
37
|
+
if (typeof code === 'string')
|
|
38
|
+
return code;
|
|
39
|
+
}
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
function errMessage(err) {
|
|
43
|
+
return err instanceof Error ? err.message : String(err);
|
|
44
|
+
}
|
|
45
|
+
const UNREACHABLE_SOCKET_CODES = new Set(['ECONNREFUSED', 'ENOTFOUND', 'EHOSTUNREACH']);
|
|
46
|
+
/** The socket errnos {@link fromPgError} maps to `PROFILE_UNREACHABLE`. `db/session.ts` asks before routing. */
|
|
47
|
+
export function isUnreachableErrno(err) {
|
|
48
|
+
const code = errCode(err);
|
|
49
|
+
return code !== undefined && UNREACHABLE_SOCKET_CODES.has(code);
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* `pg-pool`'s two `connectionTimeoutMillis` failures. Matched on message
|
|
53
|
+
* because `pg-pool` constructs a bare `Error` with no `code` for both — the
|
|
54
|
+
* literals are copied verbatim from `pg-pool@3.14.0`'s `index.js` (queue wait,
|
|
55
|
+
* and new-client connect respectively) and are covered by a unit test so a
|
|
56
|
+
* dependency bump that reworded them shows up as a failure rather than as a
|
|
57
|
+
* silent downgrade to `PG_ERROR`.
|
|
58
|
+
*/
|
|
59
|
+
const CONNECT_TIMEOUT_MESSAGES = [
|
|
60
|
+
'timeout exceeded when trying to connect',
|
|
61
|
+
'Connection terminated due to connection timeout',
|
|
62
|
+
];
|
|
63
|
+
export function isConnectTimeout(err) {
|
|
64
|
+
if (!(err instanceof Error))
|
|
65
|
+
return false;
|
|
66
|
+
return CONNECT_TIMEOUT_MESSAGES.some((m) => err.message === m);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Maps a PostgreSQL (or socket) error onto the taxonomy. SQLSTATE is always
|
|
70
|
+
* preserved in `details.sqlstate`, including for the `PG_ERROR` fallthrough.
|
|
71
|
+
* Socket errnos (`ECONNREFUSED` etc.) are not SQLSTATEs — those are preserved
|
|
72
|
+
* in `details.errno` instead, matching {@link enrichUnreachable}.
|
|
73
|
+
*/
|
|
74
|
+
export function fromPgError(err, ctx = {}) {
|
|
75
|
+
if (err instanceof PgMcpError)
|
|
76
|
+
return err;
|
|
77
|
+
const rawCode = errCode(err);
|
|
78
|
+
const isSocketCode = rawCode !== undefined && UNREACHABLE_SOCKET_CODES.has(rawCode);
|
|
79
|
+
const sqlstate = isSocketCode ? undefined : rawCode;
|
|
80
|
+
const prefix = envPrefix(ctx.profile);
|
|
81
|
+
const message = errMessage(err);
|
|
82
|
+
const details = {
|
|
83
|
+
...(sqlstate === undefined ? {} : { sqlstate }),
|
|
84
|
+
...(isSocketCode ? { errno: rawCode } : {}),
|
|
85
|
+
...(ctx.profile === undefined ? {} : { profile: ctx.profile }),
|
|
86
|
+
...(ctx.operation === undefined ? {} : { operation: ctx.operation }),
|
|
87
|
+
};
|
|
88
|
+
const make = (code, hint) => new PgMcpError({ code, message, hint, details, cause: err });
|
|
89
|
+
switch (sqlstate) {
|
|
90
|
+
case '25006': // read_only_sql_transaction
|
|
91
|
+
// Two entirely different situations share this SQLSTATE, and telling the
|
|
92
|
+
// second one to set a flag it already set sends an agent into a loop.
|
|
93
|
+
// With `ALLOW_WRITE` off, the profile is the reason. With it ON, the only
|
|
94
|
+
// way to reach 25006 is a statement whose *leading keyword* does not say
|
|
95
|
+
// it writes (`WITH x AS (INSERT … RETURNING) SELECT`, `SELECT nextval()`,
|
|
96
|
+
// a VOLATILE function that writes), so it took the read path — see
|
|
97
|
+
// `tools/execute_sql.ts`'s routing section. The fix is to rewrite the
|
|
98
|
+
// statement, not to change the environment.
|
|
99
|
+
return make('POLICY_READ_ONLY', ctx.write === true
|
|
100
|
+
? 'this profile already allows writes, so the statement reached the read-only path: its leading keyword ' +
|
|
101
|
+
'does not say it writes (WITH ... INSERT ... RETURNING, SELECT nextval(), a VOLATILE function that ' +
|
|
102
|
+
'writes), and only a statement that starts with INSERT/UPDATE/DELETE/MERGE gets the guarded write path. ' +
|
|
103
|
+
'Rewrite it as a plain INSERT/UPDATE/DELETE and re-run, so the change goes through the dry-run/confirm ' +
|
|
104
|
+
'protocol'
|
|
105
|
+
: `this profile runs writes inside a read-only transaction; set ${prefix}_ALLOW_WRITE=true for this profile and re-run`);
|
|
106
|
+
case '57014': // query_canceled (statement_timeout)
|
|
107
|
+
return make('TIMEOUT', `narrow the query, or raise ${prefix}_STATEMENT_TIMEOUT_MS and re-run`);
|
|
108
|
+
case '55P03': // lock_not_available (lock_timeout)
|
|
109
|
+
return make('LOCK_TIMEOUT', `another session holds the lock; retry once it clears, or raise ${prefix}_LOCK_TIMEOUT_MS`);
|
|
110
|
+
case '42P01': // undefined_table
|
|
111
|
+
case '42703': // undefined_column
|
|
112
|
+
return make('IDENTIFIER_UNKNOWN', 'run postgres_list_tables or postgres_describe_table to get the exact name, then re-run with it');
|
|
113
|
+
default:
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
if (isSocketCode) {
|
|
117
|
+
return make('PROFILE_UNREACHABLE', `run postgres_connection_status to see the host and port this profile resolves to, then fix ${prefix}_URL`);
|
|
118
|
+
}
|
|
119
|
+
return make('PG_ERROR', 'fix the statement reported above and re-run; PostgreSQL rejected it as written');
|
|
120
|
+
}
|
|
121
|
+
const LOOPBACK_HOSTS = new Set(['localhost', '127.0.0.1', '::1', '0.0.0.0']);
|
|
122
|
+
export function isLoopbackHost(host) {
|
|
123
|
+
return LOOPBACK_HOSTS.has(host.trim().toLowerCase().replace(/^\[|\]$/g, ''));
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* The container-networking diagnosis — the #1 support issue.
|
|
127
|
+
*
|
|
128
|
+
* Fires when the server runs in a container and the profile points at loopback,
|
|
129
|
+
* and ALSO on a bare `ECONNREFUSED` whatever the literal host: a host that was
|
|
130
|
+
* rewritten but is still wrong produces exactly the same symptom, and the
|
|
131
|
+
* remediation block is the answer in both cases.
|
|
132
|
+
*
|
|
133
|
+
* `opts.inContainer` exists purely as a test seam.
|
|
134
|
+
*/
|
|
135
|
+
export function enrichUnreachable(err, profile, opts = {}) {
|
|
136
|
+
if (err instanceof PgMcpError)
|
|
137
|
+
return err;
|
|
138
|
+
// A pool checkout that ran out of time is neither a socket errno nor a
|
|
139
|
+
// SQLSTATE, so `fromPgError` would label it `PG_ERROR` and tell the caller to
|
|
140
|
+
// fix their statement. It is one of exactly two things, and the hint says so.
|
|
141
|
+
if (isConnectTimeout(err)) {
|
|
142
|
+
return new PgMcpError({
|
|
143
|
+
code: 'PROFILE_UNREACHABLE',
|
|
144
|
+
message: `profile "${profile.alias}" could not check out a connection within PG_MCP_CONNECT_TIMEOUT_MS`,
|
|
145
|
+
hint: `either the pool is saturated (raise ${envPrefix(profile.alias)}_POOL_MAX, or run fewer concurrent calls) or ` +
|
|
146
|
+
`${profile.host}:${profile.port ?? 5432} is not answering — run postgres_connection_status to check. If this ` +
|
|
147
|
+
'fired on a single idle server, a tool took a second pool checkout while still holding the first',
|
|
148
|
+
details: {
|
|
149
|
+
profile: profile.alias,
|
|
150
|
+
host: profile.host,
|
|
151
|
+
port: profile.port ?? 5432,
|
|
152
|
+
},
|
|
153
|
+
cause: err,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
const inside = opts.inContainer ?? inContainer();
|
|
157
|
+
const code = errCode(err);
|
|
158
|
+
const loopback = isLoopbackHost(profile.host);
|
|
159
|
+
const target = `${profile.host}:${profile.port ?? 5432}`;
|
|
160
|
+
const prefix = envPrefix(profile.alias);
|
|
161
|
+
if (!(code === 'ECONNREFUSED' || (inside && loopback))) {
|
|
162
|
+
return fromPgError(err, { profile: profile.alias });
|
|
163
|
+
}
|
|
164
|
+
const message = inside && loopback
|
|
165
|
+
? `profile "${profile.alias}" points at ${target}, but this server runs inside a container where localhost is the container itself`
|
|
166
|
+
: `profile "${profile.alias}" is unreachable at ${target} (${code ?? 'connection refused'})`;
|
|
167
|
+
return new PgMcpError({
|
|
168
|
+
code: 'PROFILE_UNREACHABLE',
|
|
169
|
+
message,
|
|
170
|
+
hint: [
|
|
171
|
+
'',
|
|
172
|
+
` • DB on your host, Docker Desktop → set ${prefix}_URL host to host.docker.internal`,
|
|
173
|
+
' • DB on your host, Linux → same, plus --add-host=host.docker.internal:host-gateway',
|
|
174
|
+
' • DB in another container → --network <net> and use the service name as host',
|
|
175
|
+
].join('\n'),
|
|
176
|
+
details: {
|
|
177
|
+
profile: profile.alias,
|
|
178
|
+
host: profile.host,
|
|
179
|
+
port: profile.port ?? 5432,
|
|
180
|
+
runtime: inside ? 'container' : 'host',
|
|
181
|
+
...(code === undefined ? {} : { errno: code }),
|
|
182
|
+
},
|
|
183
|
+
cause: err,
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Converts anything into the SDK tool-result shape. Tools call this from their
|
|
188
|
+
* `catch` instead of rethrowing — see the file header for why.
|
|
189
|
+
*/
|
|
190
|
+
export function toToolResult(err) {
|
|
191
|
+
const pgErr = err instanceof PgMcpError ? err : fromPgError(err);
|
|
192
|
+
return {
|
|
193
|
+
content: [{ type: 'text', text: pgErr.toString() }],
|
|
194
|
+
isError: true,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/core/errors.ts"],"names":[],"mappings":"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"}
|