@yawlabs/postgres-mcp 0.6.18 → 0.6.20
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 +81 -0
- package/README.md +1 -1
- package/dist/index.js +97 -35
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,87 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.6.20] - 2026-06-04
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- `wrapToolHandler` now distinguishes a handler that returned a non-`ApiResponse`
|
|
14
|
+
value (null, a primitive, a raw object) from a handler that returned
|
|
15
|
+
`{ok: false}`. The former surfaces `"Error: tool handler returned a malformed
|
|
16
|
+
result (missing ok)"`; the latter still surfaces `"Error: <error>"`. A
|
|
17
|
+
misbehaving handler no longer collapses into the generic `Unknown error` path.
|
|
18
|
+
`wrapToolHandler` is also now exported from `mcp-wrapper.ts` and covered by
|
|
19
|
+
a dedicated unit test (`mcp-wrapper.test.ts`), so the production mapping is
|
|
20
|
+
exercised without standing up `index.ts` and the stdio transport.
|
|
21
|
+
- `index.ts` resolves `package.json` by walking up from the emitted module path
|
|
22
|
+
rather than via a hard-coded `"../package.json"`. A non-trivial `tsc` emit
|
|
23
|
+
layout (e.g. `dist/src/index.js`) used to crash startup with an unresolvable
|
|
24
|
+
require. Verified against the current `outDir: "dist"` layout; future layout
|
|
25
|
+
changes don't need an `index.ts` patch.
|
|
26
|
+
- Shutdown is now idempotent. SIGINT, SIGTERM, and `stdin.end` can all fire
|
|
27
|
+
near-simultaneously when a client closes the connection while the shell also
|
|
28
|
+
sends a signal. An `exiting` flag short-circuits the second and third so
|
|
29
|
+
`shutdown()` and `process.exit()` don't race.
|
|
30
|
+
- `index.ts` no longer fakes a `version` string when `__VERSION__` is unset
|
|
31
|
+
(which only happens on a plain `tsc` build, not the esbuild bundled output).
|
|
32
|
+
The new `readPackageVersion()` throws a clear error if it can't find
|
|
33
|
+
`package.json` rather than silently emitting a malformed banner.
|
|
34
|
+
|
|
35
|
+
### Security
|
|
36
|
+
- `paramValue` (used by `pg_query`, `pg_readonly`, `pg_explain` for positional
|
|
37
|
+
parameters) now uses `.finite()` on the number member, so `NaN`, `Infinity`,
|
|
38
|
+
and `-Infinity` are rejected at the MCP boundary. Without `.finite()`, pg
|
|
39
|
+
serializes the JS number to the literal string `'NaN'` / `'Infinity'`, which
|
|
40
|
+
the server happily accepts on a text column and rejects opaquely on a numeric
|
|
41
|
+
one. Now the request is rejected before it ever hits the database.
|
|
42
|
+
- `identSchema` dropped its redundant `.max(63)` so the byte-length `.refine`
|
|
43
|
+
is the sole length guard. A 64-ASCII-char string now surfaces the tailored
|
|
44
|
+
"exceeds PostgreSQL's 63-byte NAMEDATALEN limit" message instead of Zod's
|
|
45
|
+
generic "at most 63 character(s)" -- agents that read the message act on it
|
|
46
|
+
correctly.
|
|
47
|
+
|
|
48
|
+
### Added
|
|
49
|
+
- `pg_explain` rejects pre-wrapped `EXPLAIN ...` SQL with a clear hint. An LLM
|
|
50
|
+
that calls the tool with `sql: "EXPLAIN ANALYZE SELECT ..."` no longer
|
|
51
|
+
becomes the double-`EXPLAIN` syntax error from the server; the handler
|
|
52
|
+
short-circuits with "the `sql` parameter should be the query to explain, not
|
|
53
|
+
an EXPLAIN statement." Also re-applies Zod defaults (`analyze`, `format`,
|
|
54
|
+
`using`) on the direct-call path so unit tests that bypass Zod still hit
|
|
55
|
+
the documented behavior.
|
|
56
|
+
- `pg_explain` hypothetical_indexes pre-flight rejects over-qualified
|
|
57
|
+
`schema.table.extra` table names, not just pre-quoted / over-63-byte ones.
|
|
58
|
+
An `a.b.c` form previously rendered as `"a"."b"."c"` and surfaced a
|
|
59
|
+
confusing planner error; now it's rejected with a clear message.
|
|
60
|
+
- `pg_describe_table` populates `_warnings` even when the `kind` sub-query
|
|
61
|
+
returned zero rows (not just when it errored). The `?? "table"` default
|
|
62
|
+
would otherwise silently mislabel a relation that was dropped between the
|
|
63
|
+
`columns` and `kind` fetches.
|
|
64
|
+
- `pg_health` warns when the version sub-query returned zero rows (rather
|
|
65
|
+
than silently emitting `version: undefined`). Symmetric with the other
|
|
66
|
+
four sub-queries.
|
|
67
|
+
- Per-tool `inputSchema parses a type-correct sample input` test in
|
|
68
|
+
`tools.test.ts`. Catches drift between the schema's declared shape and
|
|
69
|
+
the keys the handler destructures at runtime. Uses public Zod 4
|
|
70
|
+
constructors (`instanceof z.ZodString`, etc.) -- the `_def` introspection
|
|
71
|
+
path is brittle across Zod majors.
|
|
72
|
+
|
|
73
|
+
### Docs
|
|
74
|
+
- `pg_query` description now leads with "Postgres itself is the primary
|
|
75
|
+
safety gate" and presents `ALLOW_WRITES=1` as a secondary belt-and-braces.
|
|
76
|
+
Pre-0.6.20 the order implied `ALLOW_WRITES` was the primary control, which
|
|
77
|
+
is the opposite of the recommended posture (a least-privileged role in
|
|
78
|
+
`DATABASE_URL`).
|
|
79
|
+
- `getPool()` env-var snapshot comment now leads with which two values
|
|
80
|
+
(`getMaxRows`, `isWritesAllowed`) are intentionally re-read per request,
|
|
81
|
+
with rationale, instead of burying the re-read list at the end of a
|
|
82
|
+
paragraph about the snapshot.
|
|
83
|
+
|
|
84
|
+
### Infrastructure
|
|
85
|
+
- `release.sh` accepts `REQUIRE_MATRIX=1`. When set, the existing "WSL Ubuntu
|
|
86
|
+
not detected" warning becomes a hard `fail`; default behavior is unchanged
|
|
87
|
+
(warn-only so contributors without WSL can still tag). The matrix remains
|
|
88
|
+
a local-only pre-tag gate; this is just an opt-in fail-fast.
|
|
89
|
+
- `package.json` `package-lock.json` and `server.json` all bumped to 0.6.20.
|
|
90
|
+
|
|
10
91
|
## [0.6.16] - 2026-05-18
|
|
11
92
|
|
|
12
93
|
### Tests
|
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
Built and maintained by [Yaw Labs](https://yaw.sh).
|
|
9
9
|
|
|
10
|
-
[](yaw
|
|
10
|
+
[](https://yaw.sh/mcp/install?name=Postgres&command=npx&args=-y%2C%40yawlabs%2Fpostgres-mcp&description=Query%20PostgreSQL%20-%20schema%20introspection%2C%20EXPLAIN%20plans%2C%20health%20diagnostics%2C%20read-only%20by%20default&source=https%3A%2F%2Fgithub.com%2FYawLabs%2Fpostgres-mcp)
|
|
11
11
|
|
|
12
12
|
One click adds this to your local Yaw MCP config so it's available in every Yaw Terminal session. Or install manually below.
|
|
13
13
|
|
package/dist/index.js
CHANGED
|
@@ -36349,9 +36349,62 @@ async function shutdown() {
|
|
|
36349
36349
|
}
|
|
36350
36350
|
}
|
|
36351
36351
|
|
|
36352
|
+
// src/mcp-wrapper.ts
|
|
36353
|
+
function wrapToolHandler(handler) {
|
|
36354
|
+
return async (input) => {
|
|
36355
|
+
try {
|
|
36356
|
+
const result = await handler(input);
|
|
36357
|
+
if (result === null || typeof result !== "object" || !("ok" in result)) {
|
|
36358
|
+
return {
|
|
36359
|
+
content: [
|
|
36360
|
+
{
|
|
36361
|
+
type: "text",
|
|
36362
|
+
text: "Error: tool handler returned a malformed result (missing ok)"
|
|
36363
|
+
}
|
|
36364
|
+
],
|
|
36365
|
+
isError: true
|
|
36366
|
+
};
|
|
36367
|
+
}
|
|
36368
|
+
const response = result;
|
|
36369
|
+
if (!response.ok) {
|
|
36370
|
+
return {
|
|
36371
|
+
content: [
|
|
36372
|
+
{
|
|
36373
|
+
type: "text",
|
|
36374
|
+
text: `Error: ${response.error || "Unknown error"}`
|
|
36375
|
+
}
|
|
36376
|
+
],
|
|
36377
|
+
isError: true
|
|
36378
|
+
};
|
|
36379
|
+
}
|
|
36380
|
+
const text = JSON.stringify(
|
|
36381
|
+
response.data ?? { success: true },
|
|
36382
|
+
(_k, v) => typeof v === "bigint" ? v.toString() : v,
|
|
36383
|
+
2
|
|
36384
|
+
);
|
|
36385
|
+
return {
|
|
36386
|
+
content: [{ type: "text", text }]
|
|
36387
|
+
};
|
|
36388
|
+
} catch (err) {
|
|
36389
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
36390
|
+
return {
|
|
36391
|
+
content: [{ type: "text", text: `Error: ${message}` }],
|
|
36392
|
+
isError: true
|
|
36393
|
+
};
|
|
36394
|
+
}
|
|
36395
|
+
};
|
|
36396
|
+
}
|
|
36397
|
+
|
|
36352
36398
|
// src/tools/params.ts
|
|
36353
36399
|
var paramValue = external_exports.lazy(
|
|
36354
|
-
() => external_exports.union([
|
|
36400
|
+
() => external_exports.union([
|
|
36401
|
+
external_exports.string(),
|
|
36402
|
+
external_exports.number().finite(),
|
|
36403
|
+
external_exports.boolean(),
|
|
36404
|
+
external_exports.null(),
|
|
36405
|
+
external_exports.array(paramValue),
|
|
36406
|
+
external_exports.record(external_exports.string(), paramValue)
|
|
36407
|
+
])
|
|
36355
36408
|
);
|
|
36356
36409
|
var MAX_PARAM_DEPTH = 32;
|
|
36357
36410
|
function isWithinDepth(value, maxDepth) {
|
|
@@ -36372,7 +36425,7 @@ function isWithinDepth(value, maxDepth) {
|
|
|
36372
36425
|
var paramsArray = external_exports.array(paramValue).refine((arr) => arr.every((v) => isWithinDepth(v, MAX_PARAM_DEPTH)), {
|
|
36373
36426
|
message: `Parameter value exceeds maximum nesting depth of ${MAX_PARAM_DEPTH}`
|
|
36374
36427
|
});
|
|
36375
|
-
var identSchema = external_exports.string().min(1).
|
|
36428
|
+
var identSchema = external_exports.string().min(1).refine((v) => Buffer.byteLength(v, "utf8") <= 63, {
|
|
36376
36429
|
message: "Identifier exceeds PostgreSQL's 63-byte NAMEDATALEN limit (multi-byte characters count as multiple bytes)."
|
|
36377
36430
|
});
|
|
36378
36431
|
|
|
@@ -36380,7 +36433,7 @@ var identSchema = external_exports.string().min(1).max(63).refine((v) => Buffer.
|
|
|
36380
36433
|
var adminTools = [
|
|
36381
36434
|
{
|
|
36382
36435
|
name: "pg_inspect_locks",
|
|
36383
|
-
description: "Show current lock contention: which sessions are blocked and who is blocking them. Returns blocked PID, blocking PID, lock types, relation being contested, and the queries involved. Use this first when a tool call hangs or the app feels stuck - it's the fastest way to identify a long-held transaction holding a lock. Row shape: one row per (blocked_pid, blocking_pid) pair. A session waiting on multiple blockers appears on multiple rows -- group/deduplicate by `blocked_pid` if you want a per-blocked-session count.",
|
|
36436
|
+
description: "Show current lock contention: which sessions are blocked and who is blocking them. Returns blocked PID, blocking PID, lock types, relation being contested, and the queries involved. Use this first when a tool call hangs or the app feels stuck - it's the fastest way to identify a long-held transaction holding a lock. Row shape: one row per (blocked_pid, blocking_pid) pair. A session waiting on multiple blockers appears on multiple rows -- group/deduplicate by `blocked_pid` if you want a per-blocked-session count. Caveat on `relation`: for non-relation waits (transactionid/virtualxid, where the wait is on the blocker's xid rather than a table) `relation` is a best-effort hint -- an alphabetical guess among the blocker's held write-intent locks -- not authoritative. Use the blocked/blocking query text to disambiguate which table is actually contested.",
|
|
36384
36437
|
annotations: {
|
|
36385
36438
|
title: "Inspect blocking locks",
|
|
36386
36439
|
readOnlyHint: true,
|
|
@@ -36516,7 +36569,7 @@ var adminTools = [
|
|
|
36516
36569
|
table_name AS "table",
|
|
36517
36570
|
grantee,
|
|
36518
36571
|
privilege_type,
|
|
36519
|
-
is_grantable
|
|
36572
|
+
(is_grantable = 'YES') AS is_grantable
|
|
36520
36573
|
FROM information_schema.table_privileges
|
|
36521
36574
|
WHERE table_schema = $1
|
|
36522
36575
|
${tableFilter}
|
|
@@ -36661,6 +36714,11 @@ var adminTools = [
|
|
|
36661
36714
|
// Divide in `numeric`, not `float8`: BIGINT sequences past 2^53 lose
|
|
36662
36715
|
// precision in float8, and the danger zone (>= threshold) is exactly
|
|
36663
36716
|
// where the reported pct_used must stay accurate.
|
|
36717
|
+
// Filter vs display precision differ on purpose: the WHERE clause
|
|
36718
|
+
// tests the full-precision ratio while the SELECT rounds pct_used
|
|
36719
|
+
// to numeric(6,4) for display. So a displayed 0.5000 may correspond
|
|
36720
|
+
// to a true ratio slightly above the threshold -- the filter is
|
|
36721
|
+
// correct, the display is rounded.
|
|
36664
36722
|
`SELECT
|
|
36665
36723
|
schemaname AS schema,
|
|
36666
36724
|
sequencename AS sequence,
|
|
@@ -36797,7 +36855,11 @@ function quoteQualifiedTable(name) {
|
|
|
36797
36855
|
return name.split(".").map((p) => quoteIdent(p)).join(".");
|
|
36798
36856
|
}
|
|
36799
36857
|
function validateHypoIndex(idx) {
|
|
36800
|
-
|
|
36858
|
+
const pieces = idx.table.split(".");
|
|
36859
|
+
if (pieces.length > 2) {
|
|
36860
|
+
return `Hypothetical index table ${JSON.stringify(idx.table)} is over-qualified; use only \`schema.table\` or \`table\`.`;
|
|
36861
|
+
}
|
|
36862
|
+
for (const piece of pieces) {
|
|
36801
36863
|
if (piece.includes('"')) {
|
|
36802
36864
|
return `Hypothetical index table ${JSON.stringify(idx.table)} contains a double-quote; pass plain identifier names without pre-quoting.`;
|
|
36803
36865
|
}
|
|
@@ -36858,7 +36920,15 @@ var explainTools = [
|
|
|
36858
36920
|
)
|
|
36859
36921
|
}),
|
|
36860
36922
|
handler: async (input) => {
|
|
36861
|
-
const {
|
|
36923
|
+
const {
|
|
36924
|
+
sql,
|
|
36925
|
+
analyze: rawAnalyze,
|
|
36926
|
+
format: rawFormat,
|
|
36927
|
+
params,
|
|
36928
|
+
hypothetical_indexes
|
|
36929
|
+
} = input;
|
|
36930
|
+
const analyze = rawAnalyze ?? false;
|
|
36931
|
+
const format = rawFormat ?? "text";
|
|
36862
36932
|
if (/^\s*EXPLAIN\b/i.test(sql)) {
|
|
36863
36933
|
return {
|
|
36864
36934
|
ok: false,
|
|
@@ -36898,6 +36968,9 @@ var explainTools = [
|
|
|
36898
36968
|
}
|
|
36899
36969
|
if (format === "text") {
|
|
36900
36970
|
const lines = rows.map((r) => String(r["QUERY PLAN"] ?? ""));
|
|
36971
|
+
if (result.data.truncated) {
|
|
36972
|
+
lines.push(`... [plan truncated at ${rows.length} lines; raise POSTGRES_MAX_ROWS to see the full plan]`);
|
|
36973
|
+
}
|
|
36901
36974
|
return { ok: true, data: { plan: lines.join("\n") } };
|
|
36902
36975
|
}
|
|
36903
36976
|
const jsonPlan = rows[0]?.["QUERY PLAN"];
|
|
@@ -36969,6 +37042,9 @@ var healthTools = [
|
|
|
36969
37042
|
]);
|
|
36970
37043
|
if (!versionRes.ok) return versionRes;
|
|
36971
37044
|
const warnings = [];
|
|
37045
|
+
if (versionRes.data?.[0]?.version === void 0) {
|
|
37046
|
+
warnings.push(`version unavailable despite successful query`);
|
|
37047
|
+
}
|
|
36972
37048
|
if (!sizeRes.ok) warnings.push(`database fetch failed: ${sizeRes.error}`);
|
|
36973
37049
|
if (!connsRes.ok) warnings.push(`connections fetch failed: ${connsRes.error}`);
|
|
36974
37050
|
if (!activeRes.ok) warnings.push(`active_queries fetch failed: ${activeRes.error}`);
|
|
@@ -37013,7 +37089,7 @@ var queryTools = [
|
|
|
37013
37089
|
},
|
|
37014
37090
|
{
|
|
37015
37091
|
name: "pg_query",
|
|
37016
|
-
description: "Run a SQL query against the configured PostgreSQL database.
|
|
37092
|
+
description: "Run a SQL query against the configured PostgreSQL database. Postgres itself is the primary safety gate: the role in `DATABASE_URL` enforces what queries can succeed. The recommended posture is a least-privileged role (e.g. one granted `pg_read_all_data`), which makes writes server-rejected regardless of any env var. `ALLOW_WRITES=1` is a secondary belt-and-braces gate - it lifts the in-server `BEGIN READ ONLY` wrapper, but it cannot grant privileges the role lacks. Useful for managed databases where creating a second role is awkward. For read-only access where you want the guarantee in the tool name, prefer `pg_readonly`. Use `params` for parameterized queries to avoid SQL injection. Params can be strings, numbers, booleans, null, arrays (for postgres arrays / ANY), or objects (for json/jsonb columns). Dates and UUIDs can be passed as ISO strings. Large result sets are truncated to POSTGRES_MAX_ROWS (default 1000) with a `truncated: true` flag.",
|
|
37017
37093
|
annotations: {
|
|
37018
37094
|
title: "Run SQL query",
|
|
37019
37095
|
readOnlyHint: false,
|
|
@@ -37173,6 +37249,10 @@ var schemaTools = [
|
|
|
37173
37249
|
JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
|
|
37174
37250
|
JOIN pg_catalog.pg_class cl ON cl.oid = con.confrelid
|
|
37175
37251
|
JOIN pg_catalog.pg_namespace fn ON fn.oid = cl.relnamespace
|
|
37252
|
+
-- Pairing local conkey[i] to foreign confkey[i] by ordinality relies on
|
|
37253
|
+
-- the postgres invariant that conkey[i] references confkey[i]. unnest
|
|
37254
|
+
-- WITH ORDINALITY preserves array order; reordering or dropping
|
|
37255
|
+
-- WITH ORDINALITY would silently mispair composite-FK columns.
|
|
37176
37256
|
JOIN LATERAL unnest(con.conkey) WITH ORDINALITY AS u(attnum, attposition) ON TRUE
|
|
37177
37257
|
JOIN pg_catalog.pg_attribute att ON att.attrelid = con.conrelid AND att.attnum = u.attnum
|
|
37178
37258
|
JOIN LATERAL unnest(con.confkey) WITH ORDINALITY AS fu(attnum, attposition) ON fu.attposition = u.attposition
|
|
@@ -37227,6 +37307,10 @@ var schemaTools = [
|
|
|
37227
37307
|
JOIN pg_catalog.pg_namespace srcn ON srcn.oid = src.relnamespace
|
|
37228
37308
|
JOIN pg_catalog.pg_class ref ON ref.oid = con.confrelid
|
|
37229
37309
|
JOIN pg_catalog.pg_namespace refn ON refn.oid = ref.relnamespace
|
|
37310
|
+
-- Pairing local conkey[i] to foreign confkey[i] by ordinality relies on
|
|
37311
|
+
-- the postgres invariant that conkey[i] references confkey[i]. unnest
|
|
37312
|
+
-- WITH ORDINALITY preserves array order; reordering or dropping
|
|
37313
|
+
-- WITH ORDINALITY would silently mispair composite-FK columns.
|
|
37230
37314
|
JOIN LATERAL unnest(con.conkey) WITH ORDINALITY AS u(attnum, attposition) ON TRUE
|
|
37231
37315
|
JOIN pg_catalog.pg_attribute srcatt ON srcatt.attrelid = con.conrelid AND srcatt.attnum = u.attnum
|
|
37232
37316
|
JOIN LATERAL unnest(con.confkey) WITH ORDINALITY AS fu(attnum, attposition) ON fu.attposition = u.attposition
|
|
@@ -37287,6 +37371,7 @@ var schemaTools = [
|
|
|
37287
37371
|
const kind = kindRes.ok ? kindRes.data?.[0]?.kind ?? "table" : "table";
|
|
37288
37372
|
const warnings = [];
|
|
37289
37373
|
if (!kindRes.ok) warnings.push(`kind fetch failed, reported as "table": ${kindRes.error}`);
|
|
37374
|
+
else if ((kindRes.data?.length ?? 0) === 0) warnings.push(`kind unavailable, reported as "table"`);
|
|
37290
37375
|
if (!pk.ok) warnings.push(`primary_key fetch failed: ${pk.error}`);
|
|
37291
37376
|
if (!fks.ok) warnings.push(`foreign_keys fetch failed: ${fks.error}`);
|
|
37292
37377
|
if (!idxs.ok) warnings.push(`indexes fetch failed: ${idxs.error}`);
|
|
@@ -37610,7 +37695,7 @@ function compareVersions(a, b) {
|
|
|
37610
37695
|
}
|
|
37611
37696
|
|
|
37612
37697
|
// src/index.ts
|
|
37613
|
-
var version2 = true ? "0.6.
|
|
37698
|
+
var version2 = true ? "0.6.20" : await readPackageVersion();
|
|
37614
37699
|
var subcommand = process.argv[2];
|
|
37615
37700
|
if (subcommand === "version" || subcommand === "--version") {
|
|
37616
37701
|
console.log(version2);
|
|
@@ -37627,40 +37712,17 @@ for (const tool of allTools) {
|
|
|
37627
37712
|
tool.description,
|
|
37628
37713
|
tool.inputSchema.shape,
|
|
37629
37714
|
tool.annotations,
|
|
37630
|
-
|
|
37631
|
-
try {
|
|
37632
|
-
const result = await tool.handler(input);
|
|
37633
|
-
const response = result;
|
|
37634
|
-
if (!response.ok) {
|
|
37635
|
-
return {
|
|
37636
|
-
content: [
|
|
37637
|
-
{
|
|
37638
|
-
type: "text",
|
|
37639
|
-
text: `Error: ${response.error || "Unknown error"}`
|
|
37640
|
-
}
|
|
37641
|
-
],
|
|
37642
|
-
isError: true
|
|
37643
|
-
};
|
|
37644
|
-
}
|
|
37645
|
-
const text = JSON.stringify(response.data ?? { success: true }, null, 2);
|
|
37646
|
-
return {
|
|
37647
|
-
content: [{ type: "text", text }]
|
|
37648
|
-
};
|
|
37649
|
-
} catch (err) {
|
|
37650
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
37651
|
-
return {
|
|
37652
|
-
content: [{ type: "text", text: `Error: ${message}` }],
|
|
37653
|
-
isError: true
|
|
37654
|
-
};
|
|
37655
|
-
}
|
|
37656
|
-
}
|
|
37715
|
+
wrapToolHandler(tool.handler)
|
|
37657
37716
|
);
|
|
37658
37717
|
}
|
|
37659
37718
|
var transport = new StdioServerTransport();
|
|
37660
37719
|
await server.connect(transport);
|
|
37661
37720
|
var writesNote = isWritesAllowed() ? "writes ENABLED" : "read-only";
|
|
37662
37721
|
console.error(`@yawlabs/postgres-mcp v${version2} ready (${allTools.length} tools, ${writesNote})`);
|
|
37722
|
+
var exiting = false;
|
|
37663
37723
|
var cleanup = async () => {
|
|
37724
|
+
if (exiting) return;
|
|
37725
|
+
exiting = true;
|
|
37664
37726
|
try {
|
|
37665
37727
|
await shutdown();
|
|
37666
37728
|
} catch {
|
package/package.json
CHANGED