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,212 @@
1
+ import { z } from 'zod';
2
+ import { readInt } from '../config/env.js';
3
+ import { loadProfiles } from '../config/profiles.js';
4
+ import { toToolResult } from '../core/errors.js';
5
+ import { formatOutcome } from '../core/format.js';
6
+ import { CONFIRM_PROTOCOL_DESCRIPTION, runGuardedMutation } from '../core/guards.js';
7
+ import { resolveColumn, resolveRelation } from '../core/identifiers.js';
8
+ import { getColumns, getPrimaryKey } from '../db/catalog.js';
9
+ import { withSession } from '../db/session.js';
10
+ import { arrayCast, confirmFragment, toExpectToken } from './_batch_shared.js';
11
+ import { scalarValue } from './select.js';
12
+ import { defineTool, profileParam } from './_shared.js';
13
+ /**
14
+ * `postgres_insert_batch` — bulk INSERT, still routed through
15
+ * `core/guards.ts#runGuardedMutation` for the dry-run/confirm protocol and the
16
+ * `write` capability check, even though there is no WHERE to guard.
17
+ *
18
+ * ## Two strategies, one guard path
19
+ *
20
+ * Below `PG_MCP_INSERT_BULK_THRESHOLD` rows: `INSERT … SELECT * FROM
21
+ * unnest($1::type[], $2::type[], …)` — one bound array parameter per column,
22
+ * catalog-typed via `db/catalog.ts#getColumns` (never guessed), one round
23
+ * trip for the whole batch.
24
+ *
25
+ * Above it: `INSERT … SELECT … FROM jsonb_to_recordset($1::jsonb) AS
26
+ * t(col1 type1, …)` — the whole batch collapses into ONE bound parameter
27
+ * instead of one array parameter per column, which avoids `node-postgres`'s
28
+ * per-element array-literal serialization pass (`arrayString`, string
29
+ * concatenation over every cell) becoming the bottleneck once a batch is wide
30
+ * (many columns) and long (many rows) at once.
31
+ *
32
+ * **Why not literal `COPY FROM STDIN` (design.md's original wording) above the
33
+ * threshold**, stated plainly because the deviation is deliberate, not an
34
+ * oversight: `runGuardedMutation` wraps the caller's `sql` as
35
+ * `WITH pg_admin_mcp_target AS (<sql> RETURNING …)`. `COPY` is a wire-protocol
36
+ * command, not a `RETURNING`-capable statement — it cannot appear inside that
37
+ * CTE, so it cannot be composed with the guard at all without reimplementing
38
+ * the guard's hash/row-count/checksum comparison for a second, COPY-shaped
39
+ * code path (`core/guards.ts`'s own header: compose the guard, do not
40
+ * reinvent it). Worse, `COPY` has no `RETURNING`, so a literal-COPY confirm
41
+ * could only compute a `keyChecksum` from the *input* rows rather than the
42
+ * rows actually written — silently unsafe on a table with a DB-generated
43
+ * primary key using `ON CONFLICT`-free semantics that differ from the
44
+ * unnest/jsonb path's, exactly the kind of hidden strategy-dependent safety
45
+ * hole this server's guard exists to avoid. `jsonb_to_recordset` keeps every
46
+ * safety property (RETURNING, checksum, ON CONFLICT support) byte-for-byte
47
+ * identical between both strategies.
48
+ *
49
+ * `ON CONFLICT DO NOTHING`/`DO UPDATE` are explicit opt-ins (`onConflict`);
50
+ * omitted, a conflicting row aborts the whole statement, as vanilla INSERT
51
+ * does. `DO NOTHING ... RETURNING` only returns the rows actually inserted,
52
+ * so the guard's row count/checksum reflect what was truly written either way.
53
+ */
54
+ const MAX_ROWS = 20_000;
55
+ const onConflictSchema = z
56
+ .object({
57
+ target: z
58
+ .array(z.string().min(1))
59
+ .min(1)
60
+ .optional()
61
+ .describe('Conflict target columns — must match a unique or exclusion constraint. Required by PostgreSQL when action is "update".'),
62
+ action: z.union([
63
+ z.literal('nothing'),
64
+ z.object({ update: z.array(z.string().min(1)).min(1).describe('Columns to overwrite from EXCLUDED on conflict.') }),
65
+ ]),
66
+ })
67
+ .describe('Opt-in ON CONFLICT clause. Omit for a plain INSERT that errors on any conflict.');
68
+ const inputSchema = z.object({
69
+ ...profileParam,
70
+ schema: z.string().min(1).describe('Schema name, e.g. "app".'),
71
+ table: z.string().min(1).describe('Table name within the schema.'),
72
+ columns: z.array(z.string().min(1)).min(1).describe('Column names, in the order every row supplies values.'),
73
+ rows: z
74
+ .array(z.record(z.string().min(1), scalarValue))
75
+ .min(1)
76
+ .max(MAX_ROWS)
77
+ .describe(`Rows to insert, each keyed by column name. A row missing one of "columns" binds NULL for it. Max ${MAX_ROWS} rows per call.`),
78
+ onConflict: onConflictSchema.optional(),
79
+ ...confirmFragment,
80
+ });
81
+ /**
82
+ * Pure SQL-building core of `insert_batch` — split out from the handler so
83
+ * the threshold switch and the catalog-typed cast list can be unit-tested
84
+ * without a live database (`resolveRelation`/`getColumns` need one; this
85
+ * doesn't, given already-resolved idents and a `dataTypeOf` lookup).
86
+ */
87
+ export function buildInsertSql(schemaIdent, tableIdent, columnIdents, columns, rows, dataTypeOf, threshold) {
88
+ if (rows.length > threshold) {
89
+ const jsonRows = rows.map((row) => {
90
+ const obj = {};
91
+ for (const name of columns)
92
+ obj[name] = row[name] ?? null;
93
+ return obj;
94
+ });
95
+ const fieldDefs = columns.map((name, i) => `${columnIdents[i]} ${dataTypeOf(name)}`).join(', ');
96
+ const sql = `INSERT INTO ${schemaIdent}.${tableIdent} (${columnIdents.join(', ')}) ` +
97
+ `SELECT ${columnIdents.join(', ')} FROM jsonb_to_recordset($1::jsonb) AS t(${fieldDefs})`;
98
+ return { sql, params: [JSON.stringify(jsonRows)], strategy: 'jsonb' };
99
+ }
100
+ const unnestArgs = columns.map((name, i) => `$${i + 1}::${arrayCast(dataTypeOf(name))}`).join(', ');
101
+ const aliasCols = columns.map((_, i) => `c${i}`).join(', ');
102
+ const sql = `INSERT INTO ${schemaIdent}.${tableIdent} (${columnIdents.join(', ')}) ` +
103
+ `SELECT * FROM unnest(${unnestArgs}) AS t(${aliasCols})`;
104
+ const params = columns.map((name) => rows.map((row) => row[name] ?? null));
105
+ return { sql, params, strategy: 'unnest' };
106
+ }
107
+ function buildOnConflictSql(onConflict, schema, table, knownColumns) {
108
+ if (onConflict === undefined)
109
+ return '';
110
+ const targetIdents = (onConflict.target ?? []).map((name) => resolveColumn(schema, table, name, knownColumns));
111
+ const targetSql = targetIdents.length > 0 ? ` (${targetIdents.join(', ')})` : '';
112
+ if (onConflict.action === 'nothing')
113
+ return ` ON CONFLICT${targetSql} DO NOTHING`;
114
+ const setSql = onConflict.action.update
115
+ .map((name) => resolveColumn(schema, table, name, knownColumns))
116
+ .map((ident) => `${ident} = EXCLUDED.${ident}`)
117
+ .join(', ');
118
+ return ` ON CONFLICT${targetSql} DO UPDATE SET ${setSql}`;
119
+ }
120
+ export const insertBatchTool = defineTool('postgres_insert_batch', {
121
+ title: 'Bulk INSERT rows into a PostgreSQL table',
122
+ description: 'Bulk INSERT — set-based for ordinary volumes, switching internally to a single-parameter bulk strategy above ' +
123
+ 'PG_MCP_INSERT_BULK_THRESHOLD rows (default 1000) to avoid per-column array-serialization overhead on very large ' +
124
+ 'batches; both strategies produce identical rows and go through the same guard. Column casts come from the ' +
125
+ "table's real catalog types, never guessed. `onConflict` opts into DO NOTHING or DO UPDATE SET ... FROM EXCLUDED " +
126
+ `(omitted = plain INSERT, errors on any conflict). There is no WHERE to guard, but every call still goes ` +
127
+ `through the capability check and the dry-run/confirm protocol below. If the table's primary key is ` +
128
+ `server-generated (IDENTITY/SERIAL/DEFAULT nextval) and its column is omitted from \`columns\`, the confirm ` +
129
+ `step is verified by row count only, not by key checksum — a rolled-back dry run still advances the ` +
130
+ `sequence, so the confirm's real keys would never match a previewed checksum. That holds on production too: ` +
131
+ `an INSERT's affected rows are the rows in your own parameters, which the hash already pins, so this is the ` +
132
+ `one tool a production profile may confirm on row count alone (expect and the hash stay mandatory). Include ` +
133
+ `the key column with an explicit value in \`columns\`/\`rows\` if you want checksum-pinned confirmation ` +
134
+ `anyway. ${CONFIRM_PROTOCOL_DESCRIPTION}`,
135
+ inputSchema,
136
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
137
+ }, async (args) => {
138
+ try {
139
+ const registry = loadProfiles();
140
+ const profile = registry.get(args.profile);
141
+ const rel = await resolveRelation(profile, args.schema, args.table);
142
+ // Catalog lookups run in their own session, sequentially BEFORE
143
+ // runGuardedMutation opens its own transaction — never a second
144
+ // checkout inside that transaction (see guards.ts's own doc comment).
145
+ const { columnByName, knownColumns, pkIdents, pkColumnNames } = await withSession(profile, async (client) => {
146
+ const columnInfos = await getColumns(client, rel);
147
+ const knownColumns = columnInfos.map((c) => c.name);
148
+ const primaryKey = await getPrimaryKey(client, rel);
149
+ const pkColumnNames = primaryKey?.columns ?? [];
150
+ const pkIdents = pkColumnNames.map((name) => resolveColumn(args.schema, args.table, name, knownColumns));
151
+ return { columnByName: new Map(columnInfos.map((c) => [c.name, c])), knownColumns, pkIdents, pkColumnNames };
152
+ }, { operation: 'insert_batch' });
153
+ // Only pin the guard's keyChecksum to the primary key when the caller
154
+ // supplies every PK column's value explicitly. keyChecksum exists to
155
+ // detect "the set of rows this statement affects drifted between dry
156
+ // run and confirm" — meaningful for UPDATE/DELETE, which filter
157
+ // *pre-existing* rows. INSERT has no pre-existing rows to drift: when
158
+ // the PK is server-generated (IDENTITY/SERIAL/DEFAULT nextval), the
159
+ // dry run's own INSERT still consumes real sequence values before its
160
+ // ROLLBACK (sequences are non-transactional), so the confirm's real
161
+ // INSERT is guaranteed to receive *different* generated keys than the
162
+ // dry run previewed — a false-positive GUARD_ROW_DRIFT on every single
163
+ // confirm, not a rare race. sqlHash (which hashes the input params,
164
+ // including any explicit PK values) already fully covers "did the
165
+ // input change between dry run and confirm" for INSERT, since its
166
+ // outcome depends only on its input, not on concurrent DB state the
167
+ // way a WHERE-matched row set does. So: explicit PK in the batch →
168
+ // keep the checksum (it's meaningful and stable across both calls);
169
+ // server-generated PK → skip it and rely on rowCount instead.
170
+ const keyColumns = pkColumnNames.length > 0 && pkColumnNames.every((name) => args.columns.includes(name)) ? pkIdents : [];
171
+ const columnIdents = args.columns.map((name) => resolveColumn(args.schema, args.table, name, knownColumns));
172
+ const dataTypeOf = (name) => {
173
+ const info = columnByName.get(name);
174
+ if (info === undefined) {
175
+ // resolveColumn above already validated every name in args.columns
176
+ // against knownColumns, so this is unreachable — kept as a hard
177
+ // failure rather than silently casting to a made-up type.
178
+ throw new Error(`column "${name}" resolved but has no catalog entry`);
179
+ }
180
+ return info.dataType;
181
+ };
182
+ const threshold = readInt(process.env, 'PG_MCP_INSERT_BULK_THRESHOLD', { default: 1000, min: 1 });
183
+ const built = buildInsertSql(rel.schema, rel.name, columnIdents, args.columns, args.rows, dataTypeOf, threshold);
184
+ const sql = built.sql + buildOnConflictSql(args.onConflict, args.schema, args.table, knownColumns);
185
+ const params = built.params;
186
+ const outcome = await runGuardedMutation({
187
+ profile,
188
+ capability: 'write',
189
+ operation: 'insert_batch',
190
+ sql,
191
+ params,
192
+ keyColumns,
193
+ // The other half of the paragraph above, and the reason a production
194
+ // profile can use this tool at all: with a server-generated PK,
195
+ // `keyColumns` is empty by design, and production would otherwise refuse
196
+ // the call up front for "no primary key" — a diagnosis that is false
197
+ // (the table has one) and a remedy that is impossible (GENERATED ALWAYS
198
+ // rejects an explicit value without OVERRIDING SYSTEM VALUE, which this
199
+ // tool never emits). See `core/guards.ts#noPreexistingRows` for why
200
+ // rowCount is a complete receipt for an INSERT specifically.
201
+ noPreexistingRows: true,
202
+ confirm: args.confirm,
203
+ expect: toExpectToken(args.expect),
204
+ allowRowDrift: args.allowRowDrift,
205
+ });
206
+ return formatOutcome(outcome);
207
+ }
208
+ catch (err) {
209
+ return toToolResult(err);
210
+ }
211
+ });
212
+ //# sourceMappingURL=insert_batch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"insert_batch.js","sourceRoot":"","sources":["../../src/tools/insert_batch.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,160 @@
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 { resolveSchemaScope } from '../core/identifiers.js';
6
+ import { resolveLimits } from '../core/limits.js';
7
+ import { computeShapeHash, decodeCursor, encodeCursor } from '../core/paginate.js';
8
+ import { withSession } from '../db/session.js';
9
+ import { defineTool, profileParam } from './_shared.js';
10
+ import { gatherObjects, OBJECT_KINDS } from './_object_kinds.js';
11
+ /**
12
+ * `postgres_list_objects` — the discovery entry point: schemas, tables,
13
+ * views, materialized views, sequences, functions, enum types, and
14
+ * extensions, in one paginated listing.
15
+ *
16
+ * Pagination is a plain in-memory keyset over `(schema, kind, name)`: every
17
+ * `db/catalog.ts` listing here is a `pg_catalog` metadata query (never large
18
+ * in absolute row-byte terms, even across a 5000-table database — the actual
19
+ * risk is response *token* budget, not query cost), so the whole matching set
20
+ * is gathered once, sorted deterministically, and sliced — cursor and shape
21
+ * hash come from `core/paginate.ts`'s low-level primitives directly
22
+ * (`encodeCursor`/`decodeCursor`/`computeShapeHash`) rather than its
23
+ * SQL-building helpers (`SortColumn`/`buildKeysetWhere`), which require a
24
+ * `SafeIdent` this cross-kind, in-memory merge has no use for.
25
+ */
26
+ const DEFAULT_PAGE_SIZE = 200;
27
+ const MAX_PAGE_SIZE = 2000;
28
+ const CURSOR_COLUMNS = ['schema', 'kind', 'name'];
29
+ const inputSchema = z.object({
30
+ ...profileParam,
31
+ schemas: z
32
+ .array(z.string().min(1))
33
+ .optional()
34
+ .describe('Restrict to these schemas. Omit for every schema in scope (Profile.schemas, or every non-system schema).'),
35
+ kinds: z
36
+ .array(z.enum(OBJECT_KINDS))
37
+ .optional()
38
+ .describe(`Restrict to these object kinds (${OBJECT_KINDS.join(', ')}). Omit for every kind.`),
39
+ namePattern: z
40
+ .string()
41
+ .max(200)
42
+ .optional()
43
+ .describe('SQL LIKE pattern (% and _ wildcards, \\ escapes a literal % or _), matched case-insensitively against the object name via ILIKE.'),
44
+ pageSize: z
45
+ .number()
46
+ .int()
47
+ .min(1)
48
+ .max(MAX_PAGE_SIZE)
49
+ .optional()
50
+ .describe(`Objects per page (max ${MAX_PAGE_SIZE}). Defaults to ${DEFAULT_PAGE_SIZE}, further capped by the profile row limit.`),
51
+ cursor: z.string().optional().describe('Opaque pagination cursor — pass back the previous call\'s nextCursor verbatim to fetch the next page.'),
52
+ });
53
+ function keyOf(o) {
54
+ return CURSOR_COLUMNS.map((c) => o[c]);
55
+ }
56
+ function compareKeys(a, b) {
57
+ for (let i = 0; i < a.length; i++) {
58
+ const cmp = (a[i] ?? '').localeCompare(b[i] ?? '');
59
+ if (cmp !== 0)
60
+ return cmp;
61
+ }
62
+ return 0;
63
+ }
64
+ /**
65
+ * Extracts the `(schema, kind, name)` key from a decoded cursor's sort
66
+ * values, in `CURSOR_COLUMNS` order. Requires an exact column-name match
67
+ * against `CURSOR_COLUMNS` — a cursor whose shape hash happens to match (same
68
+ * profile/schemas/kinds/namePattern) but whose `v` entries name different or
69
+ * missing columns is rejected outright rather than silently defaulting the
70
+ * absent ones to `''`, which previously restarted pagination from page 1
71
+ * (every key comparing `<= ['', '', '']`) instead of surfacing an error.
72
+ */
73
+ export function cursorKeyFromValues(values) {
74
+ const byColumn = new Map(values.map((v) => [v.column, v.value]));
75
+ const gotColumns = new Set(byColumn.keys());
76
+ const wantColumns = new Set(CURSOR_COLUMNS);
77
+ const matches = gotColumns.size === wantColumns.size && CURSOR_COLUMNS.every((c) => gotColumns.has(c));
78
+ if (!matches) {
79
+ throw new PgMcpError({
80
+ code: 'CURSOR_INVALID',
81
+ message: `pagination cursor is invalid: expected columns [${CURSOR_COLUMNS.join(', ')}], got [${[...gotColumns].join(', ')}]`,
82
+ hint: 'omit the cursor to start from the first page, or pass back the exact nextCursor value from the previous call',
83
+ });
84
+ }
85
+ return CURSOR_COLUMNS.map((c) => String(byColumn.get(c) ?? ''));
86
+ }
87
+ export const listObjectsTool = defineTool('postgres_list_objects', {
88
+ title: 'List PostgreSQL objects',
89
+ description: 'Paginated listing of schemas, tables, views, materialized views, sequences, functions, enum types, and ' +
90
+ 'extensions in scope for a profile — the entry point for discovering what exists before describing or ' +
91
+ 'querying it. Filter by schema(s), kind(s), and a name pattern. Always returns a nextCursor when more ' +
92
+ 'objects remain; pass it back verbatim as cursor to continue. ' +
93
+ 'The "enum" kind lists enum types only — domain and composite types are not covered by any kind here.',
94
+ inputSchema,
95
+ annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
96
+ }, async (args) => {
97
+ try {
98
+ const registry = loadProfiles();
99
+ const profile = registry.get(args.profile);
100
+ const filtered = await withSession(profile, async (client) => {
101
+ const schemaNames = await resolveSchemaScope(client, profile, args.schemas);
102
+ return gatherObjects(client, schemaNames, args.kinds, args.namePattern);
103
+ }, { operation: 'list_objects' });
104
+ const shapeHash = computeShapeHash({
105
+ profile: profile.alias,
106
+ schemas: args.schemas ?? null,
107
+ kinds: args.kinds ?? null,
108
+ namePattern: args.namePattern ?? null,
109
+ });
110
+ let startIndex = 0;
111
+ if (args.cursor !== undefined) {
112
+ const afterKey = cursorKeyFromValues(decodeCursor(args.cursor, shapeHash));
113
+ const idx = filtered.findIndex((o) => compareKeys(keyOf(o), afterKey) > 0);
114
+ startIndex = idx === -1 ? filtered.length : idx;
115
+ }
116
+ const limits = resolveLimits(profile.maxRows, {}, process.env);
117
+ const pageSize = Math.min(args.pageSize ?? DEFAULT_PAGE_SIZE, MAX_PAGE_SIZE, limits.maxRows);
118
+ const page = filtered.slice(startIndex, startIndex + pageSize);
119
+ const hasMore = startIndex + page.length < filtered.length;
120
+ const last = page[page.length - 1];
121
+ const nextCursor = hasMore && last !== undefined ? encodeCursor(CURSOR_COLUMNS.map((c) => ({ column: c, value: last[c] })), shapeHash) : undefined;
122
+ const rows = page.map((o) => ({
123
+ kind: o.kind,
124
+ schema: o.schema,
125
+ name: o.name,
126
+ oid: o.oid,
127
+ owner: o.owner,
128
+ comment: o.comment,
129
+ details: o.details,
130
+ }));
131
+ return formatOutcome({
132
+ kind: 'rows',
133
+ columns: [
134
+ { name: 'kind', dataTypeId: 25 },
135
+ { name: 'schema', dataTypeId: 25 },
136
+ { name: 'name', dataTypeId: 25 },
137
+ { name: 'oid', dataTypeId: 23 },
138
+ { name: 'owner', dataTypeId: 25 },
139
+ { name: 'comment', dataTypeId: 25 },
140
+ { name: 'details', dataTypeId: 114 },
141
+ ],
142
+ rows,
143
+ rowCount: filtered.length,
144
+ truncated: hasMore,
145
+ truncationNoun: 'object',
146
+ // The cap that actually cut this page is `pageSize`, never the
147
+ // profile's row cap — `gatherObjects` already returned the whole
148
+ // matching set in memory before this slice, so the row cap in
149
+ // `limits` never applies here at all (see Q5: the review measured
150
+ // the text marker misreporting "row cap 500" when pageSize:2 was
151
+ // what actually truncated the page).
152
+ ...(hasMore ? { rowCapLabel: `pageSize ${pageSize}` } : {}),
153
+ ...(nextCursor === undefined ? {} : { nextCursor }),
154
+ }, { limits });
155
+ }
156
+ catch (err) {
157
+ return toToolResult(err);
158
+ }
159
+ });
160
+ //# sourceMappingURL=list_objects.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"list_objects.js","sourceRoot":"","sources":["../../src/tools/list_objects.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,112 @@
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 { resolveLimits, truncateRows } from '../core/limits.js';
6
+ import { diffSnapshots } from '../snapshot/diff.js';
7
+ import { planMigration } from '../snapshot/migrate.js';
8
+ import { defineTool, profileParam } from './_shared.js';
9
+ import { resolveSnapshotSource, snapshotSourceSchema } from './_snapshot_io.js';
10
+ /**
11
+ * `postgres_prepare_migration` — diffs two schema snapshots (same `before`/
12
+ * `after` sources as `postgres_schema_diff`) and turns the result into an
13
+ * ordered, risk-annotated SQL plan via `snapshot/migrate.ts#planMigration`.
14
+ *
15
+ * **This tool only emits SQL text. It never executes any of it, in any way,
16
+ * on any database.** `migrate.ts` itself never imports `db/session.ts` or
17
+ * touches a `Client`/`Pool` — see that module's header — so there is no code
18
+ * path here that could run a statement even by accident. Review the plan,
19
+ * then apply it yourself statement by statement with whatever tool you trust
20
+ * for that (`postgres_execute_sql`, `psql`, a real migration runner).
21
+ *
22
+ * Each statement in the response carries: the SQL itself, a specific risk
23
+ * level and note (what could actually go wrong, not a bare label), an
24
+ * optional precondition query to run first, and either a `down` statement
25
+ * that genuinely restores the prior state or a `downNote` explaining why one
26
+ * cannot be produced honestly (a `DROP COLUMN`'s data is gone; there is no
27
+ * lying `down` for that). `migrate.ts`'s module doc lists, once, every change
28
+ * kind this generator cannot build SQL for — those surface in `unsupported`
29
+ * instead of being silently skipped.
30
+ */
31
+ const inputSchema = z.object({
32
+ ...profileParam,
33
+ before: snapshotSourceSchema.describe('The baseline side of the comparison.'),
34
+ after: snapshotSourceSchema.describe('The target side of the comparison — the plan migrates before -> after.'),
35
+ concurrentIndexes: z
36
+ .boolean()
37
+ .optional()
38
+ .describe('Use CREATE/DROP INDEX CONCURRENTLY where safe (default true). CONCURRENTLY cannot run in the same transaction ' +
39
+ 'block as any other statement in this plan — pass false if you intend to apply the plan as one transaction.'),
40
+ maxStatements: z
41
+ .number()
42
+ .int()
43
+ .positive()
44
+ .optional()
45
+ .describe('Caps the number of statements included before truncation. Defaults to the profile row limit.'),
46
+ });
47
+ /**
48
+ * `unsupported` gets the same treatment as truncation — a count and a prose
49
+ * message beside the array — deliberately. `statementCount`, `totalStatements`
50
+ * and `truncated: false` all read as "this plan is complete"; an array the
51
+ * reader has to think to inspect is not a counterweight to three of those.
52
+ */
53
+ function unsupportedHint(unsupported) {
54
+ const named = unsupported
55
+ .slice(0, 3)
56
+ .map((u) => `${u.path} (${u.objectType})`)
57
+ .join(', ');
58
+ const rest = unsupported.length > 3 ? `, and ${unsupported.length - 3} more` : '';
59
+ return (`${unsupported.length} change(s) in this diff could not be turned into SQL and are NOT in the statements below: ` +
60
+ `${named}${rest}. Applying this plan does not fully migrate before -> after — see the unsupported array for the ` +
61
+ 'reason for each, and handle those manually.');
62
+ }
63
+ /** An explicit, actionable truncation message: what was kept, what was cut, how to narrow. */
64
+ function truncationHint(kept, total, cutAt) {
65
+ const at = cutAt === undefined ? 'the cap' : `${cutAt.path} (${cutAt.objectType} ${cutAt.changeKind})`;
66
+ return `showing ${kept} of ${total} statements; truncated at ${at} — narrow before/after with a schemas scope, or raise maxStatements. Applying a truncated plan is unsafe: later statements this plan depends on may be missing.`;
67
+ }
68
+ export const prepareMigrationTool = defineTool('postgres_prepare_migration', {
69
+ title: 'Prepare an ordered SQL migration plan (SQL only — never executed)',
70
+ description: 'Diffs two schema snapshots (same before/after sources as postgres_schema_diff: live database or a file from ' +
71
+ 'postgres_schema_snapshot) and returns an ordered, dependency-safe, risk-annotated SQL plan. This tool ONLY ' +
72
+ 'emits SQL text — it never runs any of it against any database, under any circumstance. Each statement carries ' +
73
+ 'a risk level with a specific reason, an optional precondition query, and either a genuine down statement or an ' +
74
+ 'explanation of why one cannot restore the prior state. Change kinds this generator cannot build SQL for are ' +
75
+ 'listed in the response\'s unsupported array, never silently skipped. Review and apply the plan yourself, ' +
76
+ 'statement by statement, with postgres_execute_sql or another tool you trust.',
77
+ inputSchema,
78
+ annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
79
+ }, async (args) => {
80
+ try {
81
+ const registry = loadProfiles();
82
+ const profile = registry.get(args.profile);
83
+ const [before, after] = await Promise.all([
84
+ resolveSnapshotSource(args.before, args.profile),
85
+ resolveSnapshotSource(args.after, args.profile),
86
+ ]);
87
+ const changes = diffSnapshots(before, after);
88
+ const concurrentIndexes = args.concurrentIndexes ?? true;
89
+ const plan = planMigration(changes, { concurrentIndexes });
90
+ const limits = resolveLimits(profile.maxRows, args.maxStatements === undefined ? {} : { maxRows: args.maxStatements }, process.env);
91
+ const { rows: kept, truncation } = truncateRows(plan.statements, limits, (s) => JSON.stringify(s));
92
+ const truncated = truncation.reason !== null;
93
+ const data = {
94
+ before: { database: before.database },
95
+ after: { database: after.database },
96
+ concurrentIndexes,
97
+ statementCount: kept.length,
98
+ totalStatements: plan.statements.length,
99
+ statements: kept,
100
+ truncated,
101
+ ...(truncated ? { truncationMessage: truncationHint(kept.length, plan.statements.length, plan.statements[kept.length]) } : {}),
102
+ unsupportedCount: plan.unsupported.length,
103
+ ...(plan.unsupported.length > 0 ? { unsupportedMessage: unsupportedHint(plan.unsupported) } : {}),
104
+ unsupported: plan.unsupported,
105
+ };
106
+ return formatOutcome({ kind: 'report', data });
107
+ }
108
+ catch (err) {
109
+ return toToolResult(err);
110
+ }
111
+ });
112
+ //# sourceMappingURL=prepare_migration.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prepare_migration.js","sourceRoot":"","sources":["../../src/tools/prepare_migration.ts"],"names":[],"mappings":"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"}