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.
Files changed (130) hide show
  1. package/README.md +608 -2
  2. package/dist/config/env.js +67 -0
  3. package/dist/config/env.js.map +1 -0
  4. package/dist/config/profiles.js +257 -0
  5. package/dist/config/profiles.js.map +1 -0
  6. package/dist/core/audit-context.js +21 -0
  7. package/dist/core/audit-context.js.map +1 -0
  8. package/dist/core/audit.js +91 -0
  9. package/dist/core/audit.js.map +1 -0
  10. package/dist/core/brands.js +24 -0
  11. package/dist/core/brands.js.map +1 -0
  12. package/dist/core/classifier.js +279 -0
  13. package/dist/core/classifier.js.map +1 -0
  14. package/dist/core/errors.js +197 -0
  15. package/dist/core/errors.js.map +1 -0
  16. package/dist/core/format.js +258 -0
  17. package/dist/core/format.js.map +1 -0
  18. package/dist/core/guards.js +513 -0
  19. package/dist/core/guards.js.map +1 -0
  20. package/dist/core/identifiers.js +431 -0
  21. package/dist/core/identifiers.js.map +1 -0
  22. package/dist/core/limits.js +79 -0
  23. package/dist/core/limits.js.map +1 -0
  24. package/dist/core/logger.js +34 -0
  25. package/dist/core/logger.js.map +1 -0
  26. package/dist/core/paginate.js +195 -0
  27. package/dist/core/paginate.js.map +1 -0
  28. package/dist/core/paths.js +392 -0
  29. package/dist/core/paths.js.map +1 -0
  30. package/dist/core/policy.js +98 -0
  31. package/dist/core/policy.js.map +1 -0
  32. package/dist/core/shutdown.js +10 -0
  33. package/dist/core/shutdown.js.map +1 -0
  34. package/dist/core/stdout-guard.js +25 -0
  35. package/dist/core/stdout-guard.js.map +1 -0
  36. package/dist/db/catalog.js +1006 -0
  37. package/dist/db/catalog.js.map +1 -0
  38. package/dist/db/pool.js +158 -0
  39. package/dist/db/pool.js.map +1 -0
  40. package/dist/db/session.js +258 -0
  41. package/dist/db/session.js.map +1 -0
  42. package/dist/db/types.js +2 -0
  43. package/dist/db/types.js.map +1 -0
  44. package/dist/index.js +91 -0
  45. package/dist/index.js.map +1 -0
  46. package/dist/resources/index.js +88 -0
  47. package/dist/resources/index.js.map +1 -0
  48. package/dist/runtime/binaries.js +356 -0
  49. package/dist/runtime/binaries.js.map +1 -0
  50. package/dist/runtime/env-file.js +62 -0
  51. package/dist/runtime/env-file.js.map +1 -0
  52. package/dist/runtime/environment.js +34 -0
  53. package/dist/runtime/environment.js.map +1 -0
  54. package/dist/server.js +106 -0
  55. package/dist/server.js.map +1 -0
  56. package/dist/snapshot/capture.js +189 -0
  57. package/dist/snapshot/capture.js.map +1 -0
  58. package/dist/snapshot/diff.js +254 -0
  59. package/dist/snapshot/diff.js.map +1 -0
  60. package/dist/snapshot/migrate.js +1596 -0
  61. package/dist/snapshot/migrate.js.map +1 -0
  62. package/dist/snapshot/model.js +80 -0
  63. package/dist/snapshot/model.js.map +1 -0
  64. package/dist/tools/_archive_meta.js +138 -0
  65. package/dist/tools/_archive_meta.js.map +1 -0
  66. package/dist/tools/_batch_shared.js +73 -0
  67. package/dist/tools/_batch_shared.js.map +1 -0
  68. package/dist/tools/_export_format.js +107 -0
  69. package/dist/tools/_export_format.js.map +1 -0
  70. package/dist/tools/_object_kinds.js +97 -0
  71. package/dist/tools/_object_kinds.js.map +1 -0
  72. package/dist/tools/_relation_detail.js +55 -0
  73. package/dist/tools/_relation_detail.js.map +1 -0
  74. package/dist/tools/_shared.js +17 -0
  75. package/dist/tools/_shared.js.map +1 -0
  76. package/dist/tools/_snapshot_io.js +238 -0
  77. package/dist/tools/_snapshot_io.js.map +1 -0
  78. package/dist/tools/admin/_shared.js +18 -0
  79. package/dist/tools/admin/_shared.js.map +1 -0
  80. package/dist/tools/admin/extensions.js +80 -0
  81. package/dist/tools/admin/extensions.js.map +1 -0
  82. package/dist/tools/admin/grants.js +207 -0
  83. package/dist/tools/admin/grants.js.map +1 -0
  84. package/dist/tools/admin/index.js +138 -0
  85. package/dist/tools/admin/index.js.map +1 -0
  86. package/dist/tools/admin/maintenance.js +106 -0
  87. package/dist/tools/admin/maintenance.js.map +1 -0
  88. package/dist/tools/admin/roles.js +144 -0
  89. package/dist/tools/admin/roles.js.map +1 -0
  90. package/dist/tools/admin/sessions.js +104 -0
  91. package/dist/tools/admin/sessions.js.map +1 -0
  92. package/dist/tools/admin/stats.js +63 -0
  93. package/dist/tools/admin/stats.js.map +1 -0
  94. package/dist/tools/backup.js +288 -0
  95. package/dist/tools/backup.js.map +1 -0
  96. package/dist/tools/connection_status.js +138 -0
  97. package/dist/tools/connection_status.js.map +1 -0
  98. package/dist/tools/copy.js +355 -0
  99. package/dist/tools/copy.js.map +1 -0
  100. package/dist/tools/delete_batch.js +73 -0
  101. package/dist/tools/delete_batch.js.map +1 -0
  102. package/dist/tools/describe_object.js +87 -0
  103. package/dist/tools/describe_object.js.map +1 -0
  104. package/dist/tools/execute_sql.js +395 -0
  105. package/dist/tools/execute_sql.js.map +1 -0
  106. package/dist/tools/explain.js +134 -0
  107. package/dist/tools/explain.js.map +1 -0
  108. package/dist/tools/export.js +215 -0
  109. package/dist/tools/export.js.map +1 -0
  110. package/dist/tools/get_schema.js +148 -0
  111. package/dist/tools/get_schema.js.map +1 -0
  112. package/dist/tools/index.js +40 -0
  113. package/dist/tools/index.js.map +1 -0
  114. package/dist/tools/insert_batch.js +212 -0
  115. package/dist/tools/insert_batch.js.map +1 -0
  116. package/dist/tools/list_objects.js +160 -0
  117. package/dist/tools/list_objects.js.map +1 -0
  118. package/dist/tools/prepare_migration.js +112 -0
  119. package/dist/tools/prepare_migration.js.map +1 -0
  120. package/dist/tools/restore.js +301 -0
  121. package/dist/tools/restore.js.map +1 -0
  122. package/dist/tools/schema_diff.js +79 -0
  123. package/dist/tools/schema_diff.js.map +1 -0
  124. package/dist/tools/schema_snapshot.js +57 -0
  125. package/dist/tools/schema_snapshot.js.map +1 -0
  126. package/dist/tools/select.js +162 -0
  127. package/dist/tools/select.js.map +1 -0
  128. package/dist/tools/update_batch.js +154 -0
  129. package/dist/tools/update_batch.js.map +1 -0
  130. package/package.json +47 -5
@@ -0,0 +1,355 @@
1
+ import { createHash } from 'node:crypto';
2
+ import process from 'node:process';
3
+ import { Transform } from 'node:stream';
4
+ import { pipeline } from 'node:stream/promises';
5
+ import { from as copyFrom, to as copyTo } from 'pg-copy-streams';
6
+ import { z } from 'zod';
7
+ import { readInt } from '../config/env.js';
8
+ import { loadProfiles } from '../config/profiles.js';
9
+ import { toToolResult } from '../core/errors.js';
10
+ import { formatOutcome } from '../core/format.js';
11
+ import { CONFIRM_PROTOCOL_DESCRIPTION, runGuardedMutation } from '../core/guards.js';
12
+ import { columnIdentFromCatalog, resolveColumn, resolveRelation } from '../core/identifiers.js';
13
+ import { fileTooLarge, renderHostPath, resolveWorkspaceFileForRead, resolveWorkspacePath, writeFileAtomic, } from '../core/paths.js';
14
+ import { requireCapability } from '../core/policy.js';
15
+ import { getColumns } from '../db/catalog.js';
16
+ import { withSession, withTx } from '../db/session.js';
17
+ import { confirmFragment, toExpectToken } from './_batch_shared.js';
18
+ import { defineTool, profileParam } from './_shared.js';
19
+ /**
20
+ * `postgres_copy` — a whole-table (or column-subset) transfer between a table
21
+ * and a file through PostgreSQL's own COPY protocol, in both directions:
22
+ * `direction: 'export'` (`COPY … TO STDOUT` -> file, the default) and
23
+ * `direction: 'import'` (file -> `COPY … FROM STDIN`).
24
+ *
25
+ * **Why this exists next to `postgres_export`.** `postgres_export` builds its
26
+ * own CSV/JSON/JSONL text in JS (`tools/_export_format.ts`) so it can serve an
27
+ * arbitrary `SELECT`. This tool instead hands the whole job to PostgreSQL's
28
+ * own COPY protocol — no per-cell JS formatting, no server-side cursor/FETCH
29
+ * loop, just a straight byte pipe from the server to disk. That is faster for
30
+ * the case it covers (a full or column-subset table dump) but it is also why
31
+ * this tool's `source` is deliberately narrower than `postgres_export`'s:
32
+ *
33
+ * - **No raw SQL source.** `pg-copy-streams`'s `CopyStreamQuery.submit`
34
+ * issues `connection.query(this.text)` — PostgreSQL's *simple* query
35
+ * protocol, which has no bound-parameter support at all (confirmed by
36
+ * reading `copy-to.js`). A raw-SQL variant here would force either string-
37
+ * interpolating caller-supplied values into the COPY statement (a real SQL
38
+ * injection surface) or silently ignoring `params` — neither is acceptable,
39
+ * so it is not offered. `schema`/`table`/`columns` are the only inputs, and
40
+ * every one of them is resolved through `core/identifiers.ts` against
41
+ * `pg_catalog` before it reaches SQL text, exactly like `postgres_select`.
42
+ * - **No `where`.** Same reasoning again: a filter predicate is a value, and
43
+ * this statement text has nowhere safe to bind one.
44
+ *
45
+ * **Never `process.stdout.write`.** `core/stdout-guard.ts` patches
46
+ * `console.*` so an accidental log call cannot corrupt the JSON-RPC frame
47
+ * stream on stdout — but it does **not**, and cannot, patch
48
+ * `process.stdout.write` itself (that guard would also break the transport).
49
+ * A `COPY` stream piped to `process.stdout` would therefore be a real hazard,
50
+ * not a theoretical one. This tool's only destination is ever the file handle
51
+ * `core/paths.ts#writeFileAtomic` opens.
52
+ *
53
+ * **Backpressure.** `client.query(copyTo(sql))` returns a `Readable` driven
54
+ * directly off the wire protocol; `node:stream/promises#pipeline` connects it
55
+ * to the destination file with no manual `.on('data', …)` loop, so the server
56
+ * only sends more `CopyData` messages once the file write has drained —
57
+ * exactly the same discipline `postgres_export` gets from its cursor +
58
+ * `Readable.from` chain, here for free from the wire-level stream itself.
59
+ *
60
+ * **`timeoutMs`.** Same raise-then-clamp rule as `postgres_export`
61
+ * (`Profile.exportTimeoutMaxMs`), but with a different practical meaning: a
62
+ * `COPY … TO STDOUT` is one PostgreSQL statement start to finish, so
63
+ * `statement_timeout` here bounds the *entire* copy's wall-clock time — unlike
64
+ * `postgres_export`, where each `FETCH` is its own statement (see that file's
65
+ * doc comment).
66
+ *
67
+ * ## The import direction, and the one deliberate deviation
68
+ *
69
+ * `COPY … FROM` is the fastest way in this server to overwrite the contents of
70
+ * a table, so it gets the same two-call dry-run/confirm protocol as every other
71
+ * write — but it **cannot** get it the way the other write tools do.
72
+ * `core/guards.ts#runGuardedMutation` wraps the caller's statement as
73
+ * `WITH pg_admin_mcp_target AS (<sql> RETURNING pk…)`, and `COPY` is a
74
+ * wire-protocol command with no `RETURNING`: it cannot appear inside a CTE at
75
+ * all. `tools/insert_batch.ts` hit exactly this wall and answered it by not
76
+ * using COPY (it switches to `jsonb_to_recordset` above its bulk threshold).
77
+ *
78
+ * **That answer is not available here, and reusing it would delete the tool.**
79
+ * The whole reason `postgres_copy` exists next to `postgres_insert_batch` is
80
+ * that PostgreSQL parses the file: `format: text | csv | binary`, embedded
81
+ * newlines, quoting, encodings, `NULL` sentinels. Routing the import through
82
+ * `jsonb_to_recordset` would mean writing a COPY-format parser in JS — the one
83
+ * thing this tool exists to avoid — would break the byte-for-byte round trip
84
+ * with the export direction, would buffer the whole file as a JSON string, and
85
+ * is flatly impossible for `format: binary`.
86
+ *
87
+ * So this is the documented non-CTE path — **and it is still the same guard**,
88
+ * not a second one. It goes through `runGuardedMutation` via
89
+ * `core/guards.ts#GuardedExecutor`, which replaces only "how the statement is
90
+ * issued". Capability check, confirm policy, `sqlHash`, the comparison *inside
91
+ * the committing transaction*, and the `rollbackAlways` savepoint fence are all
92
+ * the shared implementation. Two consequences, both deliberate:
93
+ *
94
+ * - **`keyChecksum` is the SHA-256 of the input FILE**, computed while the
95
+ * bytes stream past on both calls, not a checksum over affected primary keys.
96
+ * For a bulk load that is the stronger of the two: a COPY has no `RETURNING`,
97
+ * and a checksum over server-generated keys would differ on every confirm
98
+ * anyway because a rolled-back dry run still advances the sequence
99
+ * (`tools/insert_batch.ts`). It means a `GUARD_ROW_DRIFT` naming a key
100
+ * checksum here reads "the file changed between the two calls".
101
+ * - **The file is streamed twice** — once for the dry run, once for the
102
+ * confirm. That is the price of previewing a load without committing it.
103
+ *
104
+ * **Input size.** `PG_MCP_MAX_IMPORT_BYTES` (default 512 MiB) is checked with
105
+ * `stat()` *before* the file is opened, and re-counted as the bytes actually
106
+ * flow so a file that grows between the two cannot slip past. It is not a
107
+ * memory bound — nothing here buffers the file — it bounds how much
108
+ * operator-supplied input this server will feed a table in one call, and it is
109
+ * sized so a load can still finish twice inside `exportTimeoutMaxMs`.
110
+ *
111
+ * **Symlinked input.** `core/paths.ts#resolveWorkspacePath` realpaths the
112
+ * *parent* directory only, because an export destination need not exist yet.
113
+ * The export direction closes the remaining gap with `writeFileAtomic`'s `wx`
114
+ * flag; an import reads, so it realpaths the file itself and puts the result
115
+ * back through the allow-list. Without that, a symlink planted inside an
116
+ * allowed directory would read whatever it points at. And because every one of
117
+ * those checks is on a *name*, `resolveWorkspaceFileForRead` opens the file once and
118
+ * the COPY streams from that **file handle** — closing the window in which the
119
+ * final path component could be swapped for a symlink after the checks and
120
+ * before the read.
121
+ */
122
+ const COPY_FORMATS = ['csv', 'text', 'binary'];
123
+ const inputSchema = z.object({
124
+ ...profileParam,
125
+ direction: z
126
+ .enum(['export', 'import'])
127
+ .optional()
128
+ .describe('export (default) = table -> file via COPY … TO STDOUT. import = file -> table via COPY … FROM STDIN, which needs PG_<ALIAS>_ALLOW_WRITE and the confirm protocol below.'),
129
+ schema: z.string().min(1),
130
+ table: z.string().min(1),
131
+ columns: z.array(z.string().min(1)).min(1).optional().describe('Columns to copy, in this order. Omit for every column, in ordinal order.'),
132
+ path: z.string().min(1).describe('File path — destination when exporting, source when importing. Relative paths resolve against the profile\'s first allowed directory; absolute paths must still land inside an allowed directory.'),
133
+ format: z.enum(COPY_FORMATS).optional().describe('PostgreSQL COPY format — defaults to csv.'),
134
+ overwrite: z.boolean().optional().describe('Export only: required (true) to replace an existing file at path. Defaults to false.'),
135
+ timeoutMs: z
136
+ .number()
137
+ .int()
138
+ .min(1)
139
+ .optional()
140
+ .describe('Per-call statement timeout, bounding the whole COPY — may raise the profile\'s default, clamped to PG_<ALIAS>_EXPORT_TIMEOUT_MAX_MS.'),
141
+ csv: z
142
+ .object({
143
+ delimiter: z.string().length(1).optional(),
144
+ header: z.boolean().optional(),
145
+ nullValue: z.string().optional(),
146
+ })
147
+ .optional()
148
+ .describe('Only applies when format is csv (the default) — passed straight through to PostgreSQL\'s own COPY … WITH options. When importing, these must describe the file you are reading.'),
149
+ ...confirmFragment,
150
+ });
151
+ /** PostgreSQL string-literal quoting for a COPY `WITH (...)` option value — this statement has no bound parameters at all (see the file header), so every option value is embedded as SQL text and must be escaped here. */
152
+ function sqlLiteral(text) {
153
+ return `'${text.replace(/'/g, "''")}'`;
154
+ }
155
+ function copyWithOptions(format, csv) {
156
+ const parts = [`FORMAT ${format}`];
157
+ if (format === 'csv') {
158
+ if (csv?.delimiter !== undefined)
159
+ parts.push(`DELIMITER ${sqlLiteral(csv.delimiter)}`);
160
+ if (csv?.header !== undefined)
161
+ parts.push(`HEADER ${csv.header ? 'true' : 'false'}`);
162
+ if (csv?.nullValue !== undefined)
163
+ parts.push(`NULL ${sqlLiteral(csv.nullValue)}`);
164
+ }
165
+ return `(${parts.join(', ')})`;
166
+ }
167
+ /**
168
+ * Sized so a load still finishes twice (dry run + confirm) inside the default
169
+ * `exportTimeoutMaxMs`, and small enough that a mistyped path cannot feed a
170
+ * multi-gigabyte blob into a table. **Not a memory bound** — nothing here
171
+ * buffers the file; it bounds blast radius and wall clock.
172
+ */
173
+ const DEFAULT_MAX_IMPORT_BYTES = 512 * 1024 * 1024;
174
+ function maxImportBytes() {
175
+ return readInt(process.env, 'PG_MCP_MAX_IMPORT_BYTES', { default: DEFAULT_MAX_IMPORT_BYTES, min: 1 });
176
+ }
177
+ /**
178
+ * The wording half of `core/paths.ts#resolveWorkspaceFileForRead`, which now
179
+ * owns the read-side path policy this file used to carry its own copy of: the
180
+ * double resolve that refuses a symlink escape, the open-once + `ino` check
181
+ * that refuses a post-check swap, and the size cap enforced from the fstat
182
+ * before a byte is read. Three copies of that policy existed before Phase 6
183
+ * (here, `tools/_snapshot_io.ts`, and `postgres_restore` would have been a
184
+ * fourth); one copy is the point, so only the sentences a human reads are
185
+ * still tool-specific.
186
+ */
187
+ function importReadOptions(cap) {
188
+ return {
189
+ maxBytes: cap,
190
+ limitLabel: 'import',
191
+ limitHint: `split the file, or raise PG_MCP_MAX_IMPORT_BYTES (currently ${cap}) and restart the server`,
192
+ missingHint: 'an import reads an existing file — check the path, and that it is inside an allowed directory',
193
+ notFileHint: 'point path at a file produced by a COPY export (or any file in the same format), not a directory or device',
194
+ };
195
+ }
196
+ /**
197
+ * The `COPY … FROM STDIN` half of the guard, as a
198
+ * `core/guards.ts#GuardedExecutor` — everything else about the two-call
199
+ * protocol is `runGuardedMutation`'s.
200
+ */
201
+ function importExecutor(sql, source, cap) {
202
+ return {
203
+ providesKeyChecksum: true,
204
+ run: async (client) => {
205
+ const digest = createHash('sha256');
206
+ let seen = 0;
207
+ // Hashes and meters in the same pass the bytes already make, so the
208
+ // content checksum costs no extra read — and a file that grew after
209
+ // `stat()` still cannot exceed the cap.
210
+ const meter = new Transform({
211
+ transform(chunk, _encoding, callback) {
212
+ seen += chunk.length;
213
+ if (seen > cap) {
214
+ callback(fileTooLarge(source.path, seen, importReadOptions(cap)));
215
+ return;
216
+ }
217
+ digest.update(chunk);
218
+ callback(null, chunk);
219
+ },
220
+ });
221
+ const dest = client.query(copyFrom(sql));
222
+ // A mid-COPY failure reaches us through `pipeline`'s rejection. The copy
223
+ // stream can then emit a SECOND error while being destroyed — it drops
224
+ // its own connection reference when it handles the first — and with no
225
+ // listener Node turns that into an uncaughtException that kills the
226
+ // server. Same absorber, same reason, as `core/paths.ts#writeFileAtomic`.
227
+ dest.on('error', () => undefined);
228
+ // `pipeline`, never a manual 'data' loop: the file is only read as fast
229
+ // as the COPY stream drains it onto the wire. The stream comes off the fd
230
+ // `resolveWorkspaceFileForRead` already validated — not off the path again, which
231
+ // could by now be a symlink to something else entirely. `run` is called
232
+ // exactly once per tool call (a dry run OR a confirm), so reading the fd
233
+ // once is enough; the second stream of the file is the second MCP call,
234
+ // with its own fd and its own checks.
235
+ await pipeline(source.handle.createReadStream({ autoClose: false }), meter, dest);
236
+ // COPY's own `COPY n` command tag — PostgreSQL's count of the rows it
237
+ // actually wrote, not our count of the lines we sent.
238
+ return { rowCount: dest.rowCount, keyChecksum: digest.digest('hex') };
239
+ },
240
+ };
241
+ }
242
+ function importNote(bytes) {
243
+ return (`The row count above is PostgreSQL's own COPY command tag — the rows it really read from the file and wrote, ` +
244
+ 'inside a transaction that was then rolled back. There is no key preview because COPY has no RETURNING. ' +
245
+ `expect.keyChecksum is the SHA-256 of the ${bytes}-byte input file, recomputed while the file streams again on ` +
246
+ 'the confirming call and compared inside the committing transaction — so if the file changes between the two ' +
247
+ 'calls the load is refused rather than applied. Confirming re-reads and re-streams the same file; COPY appends, ' +
248
+ 'it does not replace, so run this once.');
249
+ }
250
+ async function runImport(profile, args, format, timeoutMs) {
251
+ // Cheapest refusal first, and the only one that must precede any file I/O: a
252
+ // profile that cannot write never gets this server to open its input.
253
+ requireCapability('write', profile);
254
+ const cap = maxImportBytes();
255
+ const source = await resolveWorkspaceFileForRead(args.path, profile.allowedDirs, importReadOptions(cap));
256
+ try {
257
+ return await runImportGuarded(profile, args, format, timeoutMs, source, cap);
258
+ }
259
+ finally {
260
+ // Opened by `resolveWorkspaceFileForRead`; closed on every path, including a refusal
261
+ // from the guard that never reached the executor.
262
+ await source.handle.close();
263
+ }
264
+ }
265
+ async function runImportGuarded(profile, args, format, timeoutMs, source, cap) {
266
+ // Resolved BEFORE runGuardedMutation opens its transaction. `resolveRelation`
267
+ // and `withSession` each take their own pool checkout, and a second checkout
268
+ // acquired while the first is held is the Phase 2 deadlock — see the export
269
+ // branch below for the full note.
270
+ const rel = await resolveRelation(profile, args.schema, args.table);
271
+ const columnIdents = args.columns === undefined
272
+ ? []
273
+ : await withSession(profile, async (client) => {
274
+ const knownColumns = (await getColumns(client, rel)).map((c) => c.name);
275
+ return (args.columns ?? []).map((name) => resolveColumn(args.schema, args.table, name, knownColumns));
276
+ }, { operation: 'copy_import' });
277
+ const columnList = columnIdents.length === 0 ? '' : ` (${columnIdents.join(', ')})`;
278
+ const sql = `COPY ${rel.schema}.${rel.name}${columnList} FROM STDIN WITH ${copyWithOptions(format, args.csv)}`;
279
+ const outcome = await runGuardedMutation({
280
+ profile,
281
+ capability: 'write',
282
+ operation: 'copy_import',
283
+ // Hashed, not executed — the executor issues the statement. `sqlHash` must
284
+ // therefore change when the statement, the target, the format options or
285
+ // the source path changes; the file's *contents* are pinned separately by
286
+ // keyChecksum, inside the committing transaction.
287
+ sql,
288
+ params: [source.path],
289
+ keyColumns: [],
290
+ executor: importExecutor(sql, source, cap),
291
+ confirm: args.confirm,
292
+ expect: toExpectToken(args.expect),
293
+ allowRowDrift: args.allowRowDrift,
294
+ statementTimeoutMs: timeoutMs,
295
+ });
296
+ if (outcome.kind !== 'dryRun')
297
+ return formatOutcome(outcome);
298
+ return formatOutcome({ ...outcome, note: importNote(source.bytes) });
299
+ }
300
+ export const copyTool = defineTool('postgres_copy', {
301
+ title: 'COPY a table to or from a file',
302
+ description: 'High-throughput whole-table (or column-subset) transfer through PostgreSQL\'s own COPY protocol, streamed ' +
303
+ 'straight to or from a file — no where filter, no raw SQL source (see postgres_export for those). path ' +
304
+ 'resolves against the profile\'s allowed directories. direction:"export" (default) writes COPY … TO STDOUT to ' +
305
+ 'the file. direction:"import" reads the file into the table with COPY … FROM STDIN: it needs ' +
306
+ 'PG_<ALIAS>_ALLOW_WRITE=true, refuses a source file larger than PG_MCP_MAX_IMPORT_BYTES (default 512 MiB) ' +
307
+ 'before opening it, and goes through the dry-run/confirm protocol below. Two things are specific to import: ' +
308
+ 'the row count is COPY\'s own command tag, and `expect.keyChecksum` is the SHA-256 of the INPUT FILE rather ' +
309
+ 'than of affected primary keys (COPY has no RETURNING) — so a key-checksum mismatch on the confirming call ' +
310
+ 'means the file changed between the two calls. The file is streamed once for the dry run and once for the ' +
311
+ `confirm. COPY appends rows; it never replaces them. ${CONFIRM_PROTOCOL_DESCRIPTION}`,
312
+ inputSchema,
313
+ // Same annotation as `postgres_export`, deliberately: both tools write a
314
+ // file and both replace an existing one under `overwrite: true`, so both
315
+ // are destructive and neither is idempotent (a second identical call either
316
+ // refuses, or replaces a file whose contents may differ because the table
317
+ // moved underneath it).
318
+ annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true },
319
+ }, async (args) => {
320
+ try {
321
+ const registry = loadProfiles();
322
+ const profile = registry.get(args.profile);
323
+ const timeoutMs = Math.min(args.timeoutMs ?? profile.statementTimeoutMs, profile.exportTimeoutMaxMs);
324
+ const format = args.format ?? 'csv';
325
+ if (args.direction === 'import')
326
+ return await runImport(profile, args, format, timeoutMs);
327
+ const resolvedPath = await resolveWorkspacePath(args.path, profile.allowedDirs);
328
+ // Resolved BEFORE withTx takes a client: resolveRelation opens its own
329
+ // withSession, and a second pool checkout acquired while the first is
330
+ // still held is a deadlock — at poolMax: 1 it can never be satisfied at
331
+ // all, and at any poolMax that many concurrent copies wedge the profile
332
+ // until PG_MCP_CONNECT_TIMEOUT_MS fires. getColumns takes a client as an
333
+ // argument, so it is the only catalog step that may stay inside.
334
+ // `tools/select.ts` resolves before its own withTx for the same reason.
335
+ const rel = await resolveRelation(profile, args.schema, args.table);
336
+ const bytes = await withTx(profile, async (client) => {
337
+ const columnInfos = await getColumns(client, rel);
338
+ const knownColumns = columnInfos.map((c) => c.name);
339
+ const selectIdents = args.columns === undefined
340
+ ? columnInfos.map((c) => columnIdentFromCatalog(c.name))
341
+ : args.columns.map((name) => resolveColumn(args.schema, args.table, name, knownColumns));
342
+ const sql = `COPY (SELECT ${selectIdents.join(', ')} FROM ${rel.schema}.${rel.name}) TO STDOUT WITH ${copyWithOptions(format, args.csv)}`;
343
+ return writeFileAtomic(resolvedPath,
344
+ // Piped directly to the destination file handle — never to
345
+ // process.stdout. See the file header for why that guard matters here.
346
+ (dest) => pipeline(client.query(copyTo(sql)), dest), { overwrite: args.overwrite === true });
347
+ }, { readOnly: true, operation: 'copy', statementTimeoutMs: timeoutMs });
348
+ const hostPath = renderHostPath(resolvedPath, profile.allowedDirs);
349
+ return formatOutcome({ kind: 'file', path: resolvedPath, bytes, ...(hostPath === undefined ? {} : { hostPath }) });
350
+ }
351
+ catch (err) {
352
+ return toToolResult(err);
353
+ }
354
+ });
355
+ //# sourceMappingURL=copy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"copy.js","sourceRoot":"","sources":["../../src/tools/copy.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,73 @@
1
+ import { z } from 'zod';
2
+ import { loadProfiles } from '../config/profiles.js';
3
+ import { toToolResult } from '../core/errors.js';
4
+ import { formatOutcome } from '../core/format.js';
5
+ import { assertStructuredWhere, CONFIRM_PROTOCOL_DESCRIPTION, runGuardedMutation } from '../core/guards.js';
6
+ import { resolveColumn, resolveRelation } from '../core/identifiers.js';
7
+ import { getColumns, getPrimaryKey } from '../db/catalog.js';
8
+ import { withSession } from '../db/session.js';
9
+ import { buildWhereClause, confirmFragment, toExpectToken } from './_batch_shared.js';
10
+ import { wherePredicate } from './select.js';
11
+ import { defineTool, profileParam } from './_shared.js';
12
+ /**
13
+ * `postgres_delete_batch` — filtered bulk DELETE through
14
+ * `core/guards.ts#runGuardedMutation`. `where` reuses `tools/select.ts`'s
15
+ * structured predicate shape and is mandatory: `assertStructuredWhere` runs
16
+ * BEFORE `resolveRelation`, so an empty/omitted filter is refused before any
17
+ * SQL — not even an identifier lookup — reaches the server. See
18
+ * `update_batch`'s header for why the WHERE guard is "cheap and easily
19
+ * fooled" and the dry run's row count is the real guard.
20
+ */
21
+ const inputSchema = z.object({
22
+ ...profileParam,
23
+ schema: z.string().min(1).describe('Schema name, e.g. "app".'),
24
+ table: z.string().min(1).describe('Table name within the schema.'),
25
+ where: z
26
+ .array(wherePredicate)
27
+ .optional()
28
+ .describe('Filter predicates, ANDed together — required. Empty/omitted is refused before any SQL reaches the server.'),
29
+ ...confirmFragment,
30
+ });
31
+ export const deleteBatchTool = defineTool('postgres_delete_batch', {
32
+ title: 'Bulk DELETE rows from a PostgreSQL table',
33
+ description: 'Filtered bulk DELETE. `where` is a structured, parameterized filter (same shape as postgres_select) and is ' +
34
+ 'mandatory — an empty filter is refused before any SQL reaches the server; to empty a table deliberately, use ' +
35
+ `postgres_execute_sql with TRUNCATE on a profile that allows DDL. ${CONFIRM_PROTOCOL_DESCRIPTION}`,
36
+ inputSchema,
37
+ annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true },
38
+ }, async (args) => {
39
+ try {
40
+ const registry = loadProfiles();
41
+ const profile = registry.get(args.profile);
42
+ // Cheap guard first: fails before any identifier lookup, let alone any
43
+ // mutation SQL, reaches the server.
44
+ const ctx = { profile: profile.alias, operation: 'delete_batch', target: `${args.schema}.${args.table}` };
45
+ assertStructuredWhere((args.where ?? []).length, ctx);
46
+ const rel = await resolveRelation(profile, args.schema, args.table);
47
+ const { knownColumns, pkIdents } = await withSession(profile, async (client) => {
48
+ const columnInfos = await getColumns(client, rel);
49
+ const knownColumns = columnInfos.map((c) => c.name);
50
+ const primaryKey = await getPrimaryKey(client, rel);
51
+ const pkIdents = (primaryKey?.columns ?? []).map((name) => resolveColumn(args.schema, args.table, name, knownColumns));
52
+ return { knownColumns, pkIdents };
53
+ }, { operation: 'delete_batch' });
54
+ const whereResult = buildWhereClause(args.where ?? [], args.schema, args.table, knownColumns, 1);
55
+ const sql = `DELETE FROM ${rel.schema}.${rel.name} WHERE ${whereResult.clause}`;
56
+ const outcome = await runGuardedMutation({
57
+ profile,
58
+ capability: 'write',
59
+ operation: 'delete_batch',
60
+ sql,
61
+ params: whereResult.params,
62
+ keyColumns: pkIdents,
63
+ confirm: args.confirm,
64
+ expect: toExpectToken(args.expect),
65
+ allowRowDrift: args.allowRowDrift,
66
+ });
67
+ return formatOutcome(outcome);
68
+ }
69
+ catch (err) {
70
+ return toToolResult(err);
71
+ }
72
+ });
73
+ //# sourceMappingURL=delete_batch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"delete_batch.js","sourceRoot":"","sources":["../../src/tools/delete_batch.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AACrD,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,EAAE,qBAAqB,EAAE,4BAA4B,EAAE,kBAAkB,EAAqB,MAAM,mBAAmB,CAAC;AAC/H,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACxE,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAC7D,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACtF,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAExD;;;;;;;;GAQG;AAEH,MAAM,WAAW,GAAG,CAAC,CAAC,MAAM,CAAC;IAC3B,GAAG,YAAY;IACf,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,0BAA0B,CAAC;IAC9D,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,+BAA+B,CAAC;IAClE,KAAK,EAAE,CAAC;SACL,KAAK,CAAC,cAAc,CAAC;SACrB,QAAQ,EAAE;SACV,QAAQ,CAAC,2GAA2G,CAAC;IACxH,GAAG,eAAe;CACnB,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,eAAe,GAAG,UAAU,CACvC,uBAAuB,EACvB;IACE,KAAK,EAAE,0CAA0C;IACjD,WAAW,EACT,6GAA6G;QAC7G,+GAA+G;QAC/G,oEAAoE,4BAA4B,EAAE;IACpG,WAAW;IACX,WAAW,EAAE,EAAE,YAAY,EAAE,KAAK,EAAE,eAAe,EAAE,IAAI,EAAE,cAAc,EAAE,KAAK,EAAE,aAAa,EAAE,IAAI,EAAE;CACxG,EACD,KAAK,EAAE,IAAI,EAAE,EAAE;IACb,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,YAAY,EAAE,CAAC;QAChC,MAAM,OAAO,GAAG,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAE3C,uEAAuE;QACvE,oCAAoC;QACpC,MAAM,GAAG,GAAiB,EAAE,OAAO,EAAE,OAAO,CAAC,KAAK,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,KAAK,EAAE,EAAE,CAAC;QACxH,qBAAqB,CAAC,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QAEtD,MAAM,GAAG,GAAG,MAAM,eAAe,CAAC,OAAO,EAAE,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;QACpE,MAAM,EAAE,YAAY,EAAE,QAAQ,EAAE,GAAG,MAAM,WAAW,CAClD,OAAO,EACP,KAAK,EAAE,MAAM,EAAE,EAAE;YACf,MAAM,WAAW,GAAG,MAAM,UAAU,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;YAClD,MAAM,YAAY,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;YACpD,MAAM,UAAU,GAAG,MAAM,aAAa,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;YACpD,MAAM,QAAQ,GAAG,CAAC,UAAU,EAAE,OAAO,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,KAAK,EAAE,IAAI,EAAE,YAAY,CAAC,CAAC,CAAC;YACvH,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,CAAC;QACpC,CAAC,EACD,EAAE,SAAS,EAAE,cAAc,EAAE,CAC9B,CAAC;QAEF,MAAM,WAAW,GAAG,gBAAgB,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE,EAAE,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,KAAK,EAAE,YAAY,EAAE,CAAC,CAAC,CAAC;QACjG,MAAM,GAAG,GAAG,eAAe,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,IAAI,UAAU,WAAW,CAAC,MAAM,EAAE,CAAC;QAEhF,MAAM,OAAO,GAAG,MAAM,kBAAkB,CAAC;YACvC,OAAO;YACP,UAAU,EAAE,OAAO;YACnB,SAAS,EAAE,cAAc;YACzB,GAAG;YACH,MAAM,EAAE,WAAW,CAAC,MAAM;YAC1B,UAAU,EAAE,QAAQ;YACpB,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,MAAM,EAAE,aAAa,CAAC,IAAI,CAAC,MAAM,CAAC;YAClC,aAAa,EAAE,IAAI,CAAC,aAAa;SAClC,CAAC,CAAC;QAEH,OAAO,aAAa,CAAC,OAAO,CAAC,CAAC;IAChC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,YAAY,CAAC,GAAG,CAAC,CAAC;IAC3B,CAAC;AACH,CAAC,CACF,CAAC"}
@@ -0,0 +1,87 @@
1
+ import { z } from 'zod';
2
+ import { loadProfiles } from '../config/profiles.js';
3
+ import { PgMcpError, toToolResult } from '../core/errors.js';
4
+ import { formatOutcome } from '../core/format.js';
5
+ import { assertSchemaAllowed, resolveRelation } from '../core/identifiers.js';
6
+ import { getEnumTypeDetail, getFunctionDetails, getSequenceDetail } from '../db/catalog.js';
7
+ import { withSession } from '../db/session.js';
8
+ import { buildRelationDetail } from './_relation_detail.js';
9
+ import { defineTool, profileParam } from './_shared.js';
10
+ /**
11
+ * `postgres_describe_object` — full detail for one object.
12
+ *
13
+ * `resolveRelation` only covers relkinds `r,p,v,m,f` (table/partitioned
14
+ * table/view/materialized view/foreign table) — sequences (relkind `S`),
15
+ * functions (`pg_proc`), and enum types (`pg_type`) are not relations at all,
16
+ * so there is nothing for it to resolve. The strategy: try `resolveRelation`
17
+ * first (it already does schema-scope enforcement, oid lookup, and near-miss
18
+ * suggestions); if it reports the object unknown, fall back to the three
19
+ * bound-parameter lookups for the other kinds `db/catalog.ts` exposes. If
20
+ * none of those match either, the original `resolveRelation` error — with its
21
+ * suggestions — is what the caller sees.
22
+ *
23
+ * `assertSchemaAllowed` runs explicitly up front rather than relying solely
24
+ * on `resolveRelation`'s own internal check: `resolveRelation` throws the
25
+ * *same* `IDENTIFIER_UNKNOWN` code for "schema not in scope" as it does for
26
+ * "relation not found in an in-scope schema", so without this explicit gate
27
+ * the sequence/function/enum fallback below would run on a schema the
28
+ * profile is not allowed to see.
29
+ */
30
+ const inputSchema = z.object({
31
+ ...profileParam,
32
+ schema: z.string().min(1).describe('Schema name, e.g. "app".'),
33
+ name: z.string().min(1).describe('Object name within the schema — a table, view, materialized view, sequence, function, or enum type.'),
34
+ });
35
+ export const describeObjectTool = defineTool('postgres_describe_object', {
36
+ title: 'Describe a PostgreSQL object',
37
+ description: 'Full detail for one object. For a table/partitioned table: columns, primary key, foreign keys in both ' +
38
+ 'directions (outbound and what references it), unique/check constraints, indexes (with partial-index ' +
39
+ 'predicates), triggers, row-level security, permissions, and comments. A view/materialized view returns its ' +
40
+ 'definition; a sequence its parameters and current value; a function its signature and body (all overloads); ' +
41
+ 'an enum its labels. An unknown object returns IDENTIFIER_UNKNOWN with near-miss suggestions.',
42
+ inputSchema,
43
+ annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
44
+ }, async (args) => {
45
+ try {
46
+ const registry = loadProfiles();
47
+ const profile = registry.get(args.profile);
48
+ assertSchemaAllowed(profile, args.schema);
49
+ let relationError;
50
+ try {
51
+ const rel = await resolveRelation(profile, args.schema, args.name);
52
+ const detail = await withSession(profile, (client) => buildRelationDetail(client, rel), { operation: 'describe_object' });
53
+ return formatOutcome({ kind: 'report', data: { schema: args.schema, name: args.name, kind: rel.kind, detail } });
54
+ }
55
+ catch (err) {
56
+ if (err instanceof PgMcpError && err.code === 'IDENTIFIER_UNKNOWN') {
57
+ relationError = err;
58
+ }
59
+ else {
60
+ throw err;
61
+ }
62
+ }
63
+ const fallback = await withSession(profile, async (client) => {
64
+ const sequence = await getSequenceDetail(client, args.schema, args.name);
65
+ if (sequence !== null)
66
+ return { kind: 'sequence', detail: sequence };
67
+ const functions = await getFunctionDetails(client, args.schema, args.name);
68
+ if (functions.length > 0)
69
+ return { kind: 'function', detail: functions };
70
+ const enumType = await getEnumTypeDetail(client, args.schema, args.name);
71
+ if (enumType !== null)
72
+ return { kind: 'enum', detail: enumType };
73
+ return null;
74
+ }, { operation: 'describe_object' });
75
+ if (fallback === null) {
76
+ // relationError is always set by this point: the only way past the
77
+ // try/catch above without an early return is the IDENTIFIER_UNKNOWN
78
+ // catch branch running and assigning it.
79
+ throw relationError ?? new Error(`"${args.schema}.${args.name}" could not be resolved`);
80
+ }
81
+ return formatOutcome({ kind: 'report', data: { schema: args.schema, name: args.name, kind: fallback.kind, detail: fallback.detail } });
82
+ }
83
+ catch (err) {
84
+ return toToolResult(err);
85
+ }
86
+ });
87
+ //# sourceMappingURL=describe_object.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"describe_object.js","sourceRoot":"","sources":["../../src/tools/describe_object.ts"],"names":[],"mappings":"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"}