@yawlabs/postgres-mcp 0.3.0 → 0.3.2
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/dist/index.js +45 -17
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -35361,6 +35361,30 @@ async function runReadWrite(sql, params = []) {
|
|
|
35361
35361
|
client.release();
|
|
35362
35362
|
}
|
|
35363
35363
|
}
|
|
35364
|
+
async function runReadWriteRollback(sql, params = []) {
|
|
35365
|
+
if (!isWritesAllowed()) {
|
|
35366
|
+
return {
|
|
35367
|
+
ok: false,
|
|
35368
|
+
error: "Write blocked: ALLOW_WRITES is not set. Set ALLOW_WRITES=1 in the MCP server env to enable DML/DDL."
|
|
35369
|
+
};
|
|
35370
|
+
}
|
|
35371
|
+
const client = await getPool().connect();
|
|
35372
|
+
const maxRows = getMaxRows();
|
|
35373
|
+
try {
|
|
35374
|
+
await client.query("BEGIN");
|
|
35375
|
+
const result = await client.query(sql, params);
|
|
35376
|
+
await client.query("ROLLBACK");
|
|
35377
|
+
return { ok: true, data: toQueryResult(result, maxRows) };
|
|
35378
|
+
} catch (err) {
|
|
35379
|
+
try {
|
|
35380
|
+
await client.query("ROLLBACK");
|
|
35381
|
+
} catch {
|
|
35382
|
+
}
|
|
35383
|
+
return { ok: false, error: formatPgError(err) };
|
|
35384
|
+
} finally {
|
|
35385
|
+
client.release();
|
|
35386
|
+
}
|
|
35387
|
+
}
|
|
35364
35388
|
async function runInternal(sql, params = []) {
|
|
35365
35389
|
try {
|
|
35366
35390
|
const result = await getPool().query(sql, params);
|
|
@@ -35439,8 +35463,10 @@ var adminTools = [
|
|
|
35439
35463
|
}),
|
|
35440
35464
|
handler: async (input) => {
|
|
35441
35465
|
const { includeSystem } = input;
|
|
35442
|
-
const filter = includeSystem ? "" : "WHERE r.rolname
|
|
35466
|
+
const filter = includeSystem ? "" : "WHERE NOT starts_with(r.rolname, 'pg_')";
|
|
35443
35467
|
return runInternal(
|
|
35468
|
+
// Cast member_of to text[] so node-pg parses it into a JS array.
|
|
35469
|
+
// Without the cast, it comes back as the postgres text form `{a,b}`.
|
|
35444
35470
|
`SELECT
|
|
35445
35471
|
r.rolname AS name,
|
|
35446
35472
|
r.rolcanlogin AS can_login,
|
|
@@ -35450,11 +35476,11 @@ var adminTools = [
|
|
|
35450
35476
|
r.rolreplication AS replication,
|
|
35451
35477
|
r.rolbypassrls AS bypass_rls,
|
|
35452
35478
|
COALESCE(
|
|
35453
|
-
(SELECT array_agg(g.rolname ORDER BY g.rolname)
|
|
35479
|
+
(SELECT array_agg(g.rolname::text ORDER BY g.rolname)
|
|
35454
35480
|
FROM pg_catalog.pg_auth_members m
|
|
35455
35481
|
JOIN pg_catalog.pg_roles g ON g.oid = m.roleid
|
|
35456
35482
|
WHERE m.member = r.oid),
|
|
35457
|
-
ARRAY[]::
|
|
35483
|
+
ARRAY[]::text[]
|
|
35458
35484
|
) AS member_of
|
|
35459
35485
|
FROM pg_catalog.pg_roles r
|
|
35460
35486
|
${filter}
|
|
@@ -35635,14 +35661,16 @@ var adminTools = [
|
|
|
35635
35661
|
}
|
|
35636
35662
|
];
|
|
35637
35663
|
|
|
35638
|
-
// src/tools/
|
|
35664
|
+
// src/tools/params.ts
|
|
35639
35665
|
var paramValue = external_exports3.lazy(
|
|
35640
35666
|
() => external_exports3.union([external_exports3.string(), external_exports3.number(), external_exports3.boolean(), external_exports3.null(), external_exports3.array(paramValue), external_exports3.record(external_exports3.string(), paramValue)])
|
|
35641
35667
|
);
|
|
35668
|
+
|
|
35669
|
+
// src/tools/explain.ts
|
|
35642
35670
|
var explainTools = [
|
|
35643
35671
|
{
|
|
35644
35672
|
name: "pg_explain",
|
|
35645
|
-
description: "Get the query plan for a SQL statement. By default, this uses plain EXPLAIN (no execution). Set `analyze: true` to run the query with EXPLAIN ANALYZE \u2014 for non-SELECT statements, ALLOW_WRITES=1 is required (since ANALYZE actually executes the statement). Format is `text` (default) or `json`. Pass the raw SQL (not an EXPLAIN-prefixed statement).",
|
|
35673
|
+
description: "Get the query plan for a SQL statement. By default, this uses plain EXPLAIN (no execution). Set `analyze: true` to run the query with EXPLAIN ANALYZE \u2014 for non-SELECT statements, ALLOW_WRITES=1 is required (since ANALYZE actually executes the statement). Writes executed during EXPLAIN ANALYZE are always rolled back, so you can inspect a plan for an INSERT/UPDATE/DELETE without persisting the mutation. Format is `text` (default) or `json`. Pass the raw SQL (not an EXPLAIN-prefixed statement).",
|
|
35646
35674
|
annotations: {
|
|
35647
35675
|
title: "Explain query plan",
|
|
35648
35676
|
readOnlyHint: false,
|
|
@@ -35668,7 +35696,7 @@ var explainTools = [
|
|
|
35668
35696
|
if (analyze) flags.push("ANALYZE");
|
|
35669
35697
|
if (format === "json") flags.push("FORMAT JSON");
|
|
35670
35698
|
const explainSql = flags.length > 0 ? `EXPLAIN (${flags.join(", ")}) ${sql}` : `EXPLAIN ${sql}`;
|
|
35671
|
-
const result = analyze && isWritesAllowed() ? await
|
|
35699
|
+
const result = analyze && isWritesAllowed() ? await runReadWriteRollback(explainSql, params ?? []) : await runReadOnly(explainSql, params ?? []);
|
|
35672
35700
|
if (!result.ok) return result;
|
|
35673
35701
|
const data = result.data;
|
|
35674
35702
|
const rows = data?.rows ?? [];
|
|
@@ -35738,7 +35766,8 @@ var healthTools = [
|
|
|
35738
35766
|
JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
|
|
35739
35767
|
WHERE c.relkind IN ('r', 'p')
|
|
35740
35768
|
AND n.nspname NOT IN ('pg_catalog', 'information_schema')
|
|
35741
|
-
AND n.nspname NOT LIKE 'pg_toast%'
|
|
35769
|
+
AND n.nspname NOT LIKE 'pg_toast%'
|
|
35770
|
+
AND n.nspname NOT LIKE 'pg_temp_%'`
|
|
35742
35771
|
)
|
|
35743
35772
|
]);
|
|
35744
35773
|
if (!versionRes.ok) return versionRes;
|
|
@@ -35758,9 +35787,6 @@ var healthTools = [
|
|
|
35758
35787
|
];
|
|
35759
35788
|
|
|
35760
35789
|
// src/tools/query.ts
|
|
35761
|
-
var paramValue2 = external_exports3.lazy(
|
|
35762
|
-
() => external_exports3.union([external_exports3.string(), external_exports3.number(), external_exports3.boolean(), external_exports3.null(), external_exports3.array(paramValue2), external_exports3.record(external_exports3.string(), paramValue2)])
|
|
35763
|
-
);
|
|
35764
35790
|
var queryTools = [
|
|
35765
35791
|
{
|
|
35766
35792
|
name: "pg_query",
|
|
@@ -35775,7 +35801,7 @@ var queryTools = [
|
|
|
35775
35801
|
},
|
|
35776
35802
|
inputSchema: external_exports3.object({
|
|
35777
35803
|
sql: external_exports3.string().min(1).max(1e6).describe("The SQL statement to execute. Hard cap of 1 MB."),
|
|
35778
|
-
params: external_exports3.array(
|
|
35804
|
+
params: external_exports3.array(paramValue).optional().describe("Positional parameters referenced as $1, $2, ... in the SQL.")
|
|
35779
35805
|
}),
|
|
35780
35806
|
handler: async (input) => {
|
|
35781
35807
|
const { sql, params } = input;
|
|
@@ -35902,10 +35928,10 @@ var schemaTools = [
|
|
|
35902
35928
|
const foreignKeysQuery = `
|
|
35903
35929
|
SELECT
|
|
35904
35930
|
con.conname AS constraint_name,
|
|
35905
|
-
array_agg(att.attname ORDER BY u.attposition) AS columns,
|
|
35931
|
+
array_agg(att.attname::text ORDER BY u.attposition) AS columns,
|
|
35906
35932
|
cl.relname AS foreign_table,
|
|
35907
35933
|
fn.nspname AS foreign_schema,
|
|
35908
|
-
array_agg(fatt.attname ORDER BY u.attposition) AS foreign_columns
|
|
35934
|
+
array_agg(fatt.attname::text ORDER BY u.attposition) AS foreign_columns
|
|
35909
35935
|
FROM pg_catalog.pg_constraint con
|
|
35910
35936
|
JOIN pg_catalog.pg_class c ON c.oid = con.conrelid
|
|
35911
35937
|
JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
|
|
@@ -36190,9 +36216,8 @@ var statsTools = [
|
|
|
36190
36216
|
n_live_tup::text AS live_tuples,
|
|
36191
36217
|
seq_tup_read::text AS seq_tup_read,
|
|
36192
36218
|
CASE
|
|
36193
|
-
WHEN COALESCE(idx_scan, 0) = 0
|
|
36194
|
-
|
|
36195
|
-
ELSE (seq_scan::numeric / NULLIF(idx_scan, 0))::numeric(10, 2)::float8
|
|
36219
|
+
WHEN COALESCE(idx_scan, 0) = 0 THEN NULL
|
|
36220
|
+
ELSE (seq_scan::numeric / idx_scan)::numeric(10, 2)::float8
|
|
36196
36221
|
END AS ratio
|
|
36197
36222
|
FROM pg_catalog.pg_stat_user_tables
|
|
36198
36223
|
WHERE n_live_tup >= $1
|
|
@@ -36258,7 +36283,7 @@ function compareVersions(a, b) {
|
|
|
36258
36283
|
}
|
|
36259
36284
|
|
|
36260
36285
|
// src/index.ts
|
|
36261
|
-
var version2 = true ? "0.3.
|
|
36286
|
+
var version2 = true ? "0.3.2" : (await null).createRequire(import.meta.url)("../package.json").version;
|
|
36262
36287
|
var subcommand = process.argv[2];
|
|
36263
36288
|
if (subcommand === "version" || subcommand === "--version") {
|
|
36264
36289
|
console.log(version2);
|
|
@@ -36320,4 +36345,7 @@ process.on("SIGINT", () => {
|
|
|
36320
36345
|
process.on("SIGTERM", () => {
|
|
36321
36346
|
void cleanup().finally(() => process.exit(0));
|
|
36322
36347
|
});
|
|
36348
|
+
process.stdin.on("end", () => {
|
|
36349
|
+
void cleanup().finally(() => process.exit(0));
|
|
36350
|
+
});
|
|
36323
36351
|
//# sourceMappingURL=index.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yawlabs/postgres-mcp",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"description": "PostgreSQL MCP server — query, schema introspection, explain, and health checks for AI assistants",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "YawLabs <contact@yaw.sh>",
|
|
@@ -31,9 +31,9 @@
|
|
|
31
31
|
"build": "tsc && node build.mjs",
|
|
32
32
|
"dev": "tsc --watch",
|
|
33
33
|
"start": "node dist/index.js",
|
|
34
|
-
"test": "npm run build && node
|
|
34
|
+
"test": "npm run build && node scripts/run-tests.mjs dist",
|
|
35
35
|
"test:ci": "npm run test",
|
|
36
|
-
"test:integration": "npm run build && node
|
|
36
|
+
"test:integration": "npm run build && node scripts/run-tests.mjs dist --integration",
|
|
37
37
|
"lint": "biome check src/",
|
|
38
38
|
"lint:fix": "biome check --write src/",
|
|
39
39
|
"prepublishOnly": "npm run build"
|