@yawlabs/postgres-mcp 0.6.11 → 0.6.13
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/CHANGELOG.md +32 -0
- package/dist/index.js +55 -14
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,38 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.6.13] - 2026-05-16
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- `pg_kill` now captures postgres's NOTICE channel during
|
|
14
|
+
`pg_cancel_backend` / `pg_terminate_backend` and surfaces the message in
|
|
15
|
+
the `note` field when `signaled=false`. Postgres distinguishes "PID N
|
|
16
|
+
is not a PostgreSQL backend process" from "must be a member of the role
|
|
17
|
+
whose query is being canceled or member of pg_signal_backend" via
|
|
18
|
+
NOTICE, but the boolean return collapses both to `false`. Pre-0.6.13
|
|
19
|
+
the handler returned a generic three-way list and the agent had to
|
|
20
|
+
guess; now the cause is in the response. Closes #6.
|
|
21
|
+
|
|
22
|
+
### Docs
|
|
23
|
+
- `pg_kill` description now documents the NOTICE-derived `note` field.
|
|
24
|
+
|
|
25
|
+
### Internal
|
|
26
|
+
- `formatPgError` is now exported from `api.ts` so handlers that bypass
|
|
27
|
+
`runInternal` / `withSharedClient` (currently only `pg_kill`) can format
|
|
28
|
+
errors consistently with the rest of the codebase.
|
|
29
|
+
|
|
30
|
+
## [0.6.12] - 2026-05-16
|
|
31
|
+
|
|
32
|
+
### Fixed
|
|
33
|
+
- `pg_inspect_locks` now resolves `relation` for the common case of row-level
|
|
34
|
+
contention. Previously, `SELECT FOR UPDATE` + `UPDATE` row-level waits
|
|
35
|
+
queued on a `transactionid` lock with `pg_locks.relation = NULL`, so the
|
|
36
|
+
handler returned `relation: null` and gave the agent no hint as to which
|
|
37
|
+
table was contested. A fallback subquery now resolves the contested table
|
|
38
|
+
from the blocker's held write-intent relation locks (RowShareLock,
|
|
39
|
+
RowExclusiveLock, etc.), filtering out plain SELECT's AccessShareLock so
|
|
40
|
+
unrelated tables the blocker only read from don't show up. Closes #5.
|
|
41
|
+
|
|
10
42
|
## [0.6.11] - 2026-05-16
|
|
11
43
|
|
|
12
44
|
### Infrastructure
|
package/dist/index.js
CHANGED
|
@@ -36391,7 +36391,35 @@ var adminTools = [
|
|
|
36391
36391
|
FROM pg_catalog.pg_class c
|
|
36392
36392
|
JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
|
|
36393
36393
|
WHERE c.oid = bl.relation)
|
|
36394
|
-
ELSE
|
|
36394
|
+
ELSE (
|
|
36395
|
+
-- transactionid / virtualxid waits have bl.relation = NULL
|
|
36396
|
+
-- because the wait is on the blocker's xid, not on a relation.
|
|
36397
|
+
-- The contested table is usually identifiable from the
|
|
36398
|
+
-- blocker's held write-intent locks: SELECT FOR UPDATE takes
|
|
36399
|
+
-- RowShareLock, UPDATE/INSERT/DELETE take RowExclusiveLock,
|
|
36400
|
+
-- migrations take stronger modes. Filter out the AccessShare
|
|
36401
|
+
-- locks (plain SELECT) the blocker also holds on every table
|
|
36402
|
+
-- they read from -- those aren't the contention source. If
|
|
36403
|
+
-- the blocker has touched multiple write-intent tables in
|
|
36404
|
+
-- their transaction, this is a best-effort hint, not a
|
|
36405
|
+
-- definitive answer; the blocked/blocking queries above let
|
|
36406
|
+
-- the caller disambiguate.
|
|
36407
|
+
SELECT n.nspname || '.' || c.relname
|
|
36408
|
+
FROM pg_catalog.pg_locks blocker_locks
|
|
36409
|
+
JOIN pg_catalog.pg_class c ON c.oid = blocker_locks.relation
|
|
36410
|
+
JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
|
|
36411
|
+
WHERE blocker_locks.pid = blocking.pid
|
|
36412
|
+
AND blocker_locks.locktype = 'relation'
|
|
36413
|
+
AND blocker_locks.granted
|
|
36414
|
+
AND blocker_locks.mode IN (
|
|
36415
|
+
'RowShareLock', 'RowExclusiveLock', 'ShareLock',
|
|
36416
|
+
'ShareRowExclusiveLock', 'ShareUpdateExclusiveLock',
|
|
36417
|
+
'ExclusiveLock', 'AccessExclusiveLock'
|
|
36418
|
+
)
|
|
36419
|
+
AND n.nspname NOT IN ('pg_catalog', 'information_schema')
|
|
36420
|
+
ORDER BY n.nspname, c.relname
|
|
36421
|
+
LIMIT 1
|
|
36422
|
+
)
|
|
36395
36423
|
END AS relation,
|
|
36396
36424
|
bl.locktype AS lock_type
|
|
36397
36425
|
FROM pg_catalog.pg_locks bl
|
|
@@ -36480,7 +36508,7 @@ var adminTools = [
|
|
|
36480
36508
|
},
|
|
36481
36509
|
{
|
|
36482
36510
|
name: "pg_kill",
|
|
36483
|
-
description: "Cancel a running query (SIGINT-equivalent) or terminate a backend connection (SIGTERM-equivalent) by PID. Find the PID via `pg_health` active_queries or `pg_inspect_locks`. Requires ALLOW_WRITES=1 since this changes database session state. The role in DATABASE_URL must have permission - cancelling another user's query needs the `pg_signal_backend` role or superuser. Note: `pg_signal_backend` does NOT cover superuser-owned backends - only a superuser can signal another superuser's session. Cancel is graceful; terminate is forceful.",
|
|
36511
|
+
description: "Cancel a running query (SIGINT-equivalent) or terminate a backend connection (SIGTERM-equivalent) by PID. Find the PID via `pg_health` active_queries or `pg_inspect_locks`. Requires ALLOW_WRITES=1 since this changes database session state. The role in DATABASE_URL must have permission - cancelling another user's query needs the `pg_signal_backend` role or superuser. Note: `pg_signal_backend` does NOT cover superuser-owned backends - only a superuser can signal another superuser's session. Cancel is graceful; terminate is forceful. When `signaled=false`, the `note` field surfaces postgres's NOTICE explaining why (e.g. 'not a PostgreSQL backend process' for a non-pg PID, 'must be a member of...' for permission denial) so an agent can act on the specific cause rather than guess from a three-way list.",
|
|
36484
36512
|
annotations: {
|
|
36485
36513
|
title: "Cancel or terminate a backend",
|
|
36486
36514
|
readOnlyHint: false,
|
|
@@ -36501,18 +36529,31 @@ var adminTools = [
|
|
|
36501
36529
|
};
|
|
36502
36530
|
}
|
|
36503
36531
|
const fn = mode === "terminate" ? "pg_terminate_backend" : "pg_cancel_backend";
|
|
36504
|
-
const
|
|
36505
|
-
|
|
36506
|
-
const
|
|
36507
|
-
|
|
36508
|
-
ok: true,
|
|
36509
|
-
data: {
|
|
36510
|
-
pid,
|
|
36511
|
-
mode,
|
|
36512
|
-
signaled,
|
|
36513
|
-
note: signaled ? `Sent ${mode === "terminate" ? "SIGTERM" : "SIGINT"} to backend ${pid}.` : `Signal returned false - PID ${pid} may not exist, may already be gone, or the current role lacks permission.`
|
|
36514
|
-
}
|
|
36532
|
+
const client = await getPool().connect();
|
|
36533
|
+
const notices = [];
|
|
36534
|
+
const onNotice = (n) => {
|
|
36535
|
+
if (n.message) notices.push(n.message);
|
|
36515
36536
|
};
|
|
36537
|
+
client.on("notice", onNotice);
|
|
36538
|
+
try {
|
|
36539
|
+
const result = await client.query(`SELECT ${fn}($1) AS signaled`, [pid]);
|
|
36540
|
+
const signaled = result.rows[0]?.signaled === true;
|
|
36541
|
+
const noticeText = notices.join(" ").trim();
|
|
36542
|
+
return {
|
|
36543
|
+
ok: true,
|
|
36544
|
+
data: {
|
|
36545
|
+
pid,
|
|
36546
|
+
mode,
|
|
36547
|
+
signaled,
|
|
36548
|
+
note: signaled ? `Sent ${mode === "terminate" ? "SIGTERM" : "SIGINT"} to backend ${pid}.` : noticeText ? `${noticeText} (Signal returned false for PID ${pid}.)` : `Signal returned false - PID ${pid} may not exist, may already be gone, or the current role lacks permission.`
|
|
36549
|
+
}
|
|
36550
|
+
};
|
|
36551
|
+
} catch (err) {
|
|
36552
|
+
return { ok: false, error: formatPgError(err) };
|
|
36553
|
+
} finally {
|
|
36554
|
+
client.off("notice", onNotice);
|
|
36555
|
+
client.release();
|
|
36556
|
+
}
|
|
36516
36557
|
}
|
|
36517
36558
|
},
|
|
36518
36559
|
{
|
|
@@ -37550,7 +37591,7 @@ function compareVersions(a, b) {
|
|
|
37550
37591
|
}
|
|
37551
37592
|
|
|
37552
37593
|
// src/index.ts
|
|
37553
|
-
var version2 = true ? "0.6.
|
|
37594
|
+
var version2 = true ? "0.6.13" : (await null).createRequire(import.meta.url)("../package.json").version;
|
|
37554
37595
|
var subcommand = process.argv[2];
|
|
37555
37596
|
if (subcommand === "version" || subcommand === "--version") {
|
|
37556
37597
|
console.log(version2);
|
package/package.json
CHANGED