Skip to content

Releases

v0.13.12

Choose a tag to compare

@jeffyaw jeffyaw released this 12 Nov 03:20
v0.13.12
3d79263

Changed

  • The oam floor moves from 0.19.1 to 0.19.5, so the postgres-mcp launcher
    no longer runs the server on an older oam.
    0.19.5 is verified through the
    launcher with OAM_BIN pointing at it and a local PostgreSQL 18.3: an MCP
    handshake listing all 23 tools and a pg_readonly query, under
    POSTGRES_MCP_RUNTIME=oam and again under POSTGRES_MCP_SANDBOX=1, with
    the server process confirmed as that binary (oam run .../dist/index.js,
    and oam --permission --allow-net=localhost:5432 ... run for the sandbox).
    An oam 0.19.4 set as OAM_BIN is now passed over for a 0.19.5 when one is
    installed, and otherwise refused (RUNTIME=oam, the sandbox) or replaced by
    Node (auto). The NODE_OPTIONS permission-flag measurements behind the
    launcher's strip were re-run on 0.19.5 and still hold.

Fixed

  • The sandbox docs no longer claim network access is limited to the database
    endpoint.
    From oam 0.19.3, dns.setServers() works under --permission
    with no net check, so sandboxed code can send DNS queries to any server it
    names. Measured on 0.19.5 with no --allow-net: a query carrying a chosen
    name reached a listener on an arbitrary local port. The README now says TCP
    connections are pinned to the one host and port, and that the sandbox is not
    an exfiltration boundary. Two launcher comments that described oam
    behavior that has since changed are corrected too: a permission flag written
    after run has been lifted and applied since oam 0.19.4 instead of rejected,
    and a repeated list flag has added to the earlier grant since 0.19.1 instead
    of replacing it.

v0.13.11

Choose a tag to compare

@jeffyaw jeffyaw released this 12 Nov 02:50
v0.13.11
688e41f

Security

  • The bundled MCP server SDK moves from 2.0.0 to 2.4.0, and zod from 4.4.3
    to 4.6.7.
    This clears GHSA advisories for @modelcontextprotocol/server
    < 2.3.0 (cross-client message delivery on shared HTTP transports; bearer
    tokens issued for another service accepted) and zod < 4.6.6 (memory
    exhaustion validating oversized constrained collections). postgres-mcp
    serves over stdio only and does no bearer auth, so neither SDK issue was
    reachable here; both packages are bundled into dist/, so the fix ships
    with the next release.

v0.13.10

Choose a tag to compare

@jeffyaw jeffyaw released this 31 Oct 02:58
v0.13.10
01f8597

Changed

  • The oam floor moves from 0.18.0 to 0.19.1, so the postgres-mcp launcher
    no longer runs the server on an older oam.
    0.19.1 is verified through the
    launcher with OAM_BIN pointing at it and a local PostgreSQL 18.3: an MCP
    handshake listing all 23 tools and a pg_readonly query, under
    POSTGRES_MCP_RUNTIME=oam and again under POSTGRES_MCP_SANDBOX=1, with
    the server process confirmed as that binary (oam run .../dist/index.js,
    and oam --permission --allow-net=localhost:5432 ... run for the sandbox).
    0.19.0 and its -dev builds now sit below the floor.
  • ALLOW_WRITES and POSTGRES_SSL_REJECT_UNAUTHORIZED are trimmed and
    case-insensitive.
    ALLOW_WRITES=TRUE or "1 " now allows writes, where it
    used to stay read-only without a word. Any value other than 1, true or
    an off value (0, false, off, empty) still stays read-only, and now
    says so once on stderr.

Security

  • The sandbox's NODE_OPTIONS strip is now the only thing keeping an
    inherited grant out.
    From oam 0.19.1 (#363) a --allow-fs-read,
    --allow-fs-write, --allow-net or --allow-env in NODE_OPTIONS adds
    its entries to the command line's instead of yielding to them: re-measured,
    NODE_OPTIONS=--allow-fs-read=<file> beside the launcher's own
    --allow-fs-read list reads that file too. The launcher already removed
    every such token from the sandboxed oam's environment; the comments and
    README now say why that is load-bearing, and a unit test pins the strip for
    each list flag.

Fixed

  • scripts/check-oam-floor.mjs runs from a junctioned or symlinked
    checkout.
    It decided whether it was the main module by comparing
    resolve(process.argv[1]) with its own path, but Node realpaths the main
    module and not argv[1], so through a link the check was skipped and the
    script exited 0 with no output -- including from release.sh. Both sides
    are now realpath'd.

v0.13.9

Choose a tag to compare

@jeffyaw jeffyaw released this 11 Oct 15:31
v0.13.9
f20f574

Security

  • POSTGRES_MCP_SANDBOX=1 no longer lets an inherited NODE_OPTIONS widen
    the sandbox.
    oam 0.18.0 reads --permission and --allow-* flags from
    NODE_OPTIONS as well as from its command line, and takes every one the
    command line leaves out from there: measured,
    NODE_OPTIONS=--allow-fs-read=* beside the launcher's
    oam --permission ... run let the server read any file. The launcher now
    removes those flags from the sandboxed oam's copy of NODE_OPTIONS -- judged
    on the value oam reads, so a quoted "--allow-fs-read=*" goes too -- and
    keeps every other token.

Fixed

  • A Node handoff from an oam 0.18.0 host running under --permission now
    says why it cannot work, instead of Node exiting 9.
    oam 0.18.0 hands its
    --permission and --allow-* flags to every child in NODE_OPTIONS, after
    any environment the launcher passes, and Node rejects the oam-only ones
    (--allow-net= is not allowed in NODE_OPTIONS). With
    POSTGRES_MCP_RUNTIME=node on such a host the launcher now exits with a
    message naming the fix. Every other Node handoff gets a NODE_OPTIONS with
    oam's permission flags removed, so one inherited from an oam further up the
    tree does not stop Node either.
  • "No usable oam" now names the fix that fits what was found. An oam that
    is too old gets "Run oam self-update to get oam 0.18.0 or newer", one that
    would not run gets a check of the binary, and a missing OAM_BIN gets a
    pointer to fix it. Only when no oam was found at all does the message say to
    install from oamjs.org -- and on Linux off x64, where oam publishes no
    build, it says so instead. Both messages changed: POSTGRES_MCP_RUNTIME=oam
    and POSTGRES_MCP_SANDBOX=1. They used to say "Install or update oam from
    https://oamjs.org" in every case.
  • The Unix-socket sandbox refusal no longer says oam cannot connect to a
    socket.
    oam has dialled Unix sockets since 0.18.0; what the sandbox cannot
    do is pin one, because the launcher only grants a TCP host and port. The
    refusal now says that, and its remedy is "connect over TCP, or unset
    POSTGRES_MCP_SANDBOX" -- it no longer sends you to
    POSTGRES_MCP_RUNTIME=node, which the socket does not need.
  • The launcher finds an oam installed to OAM_INSTALL_DIR. That variable
    is where oam's installers and oam self-update put the binary, but discovery
    only checked the default directories and PATH, so an oam installed
    somewhere custom and not on PATH was never found. $OAM_INSTALL_DIR is now
    checked first.

Added

  • An integration test runs the sandbox against a real oam and database.
    With OAM_BIN and DATABASE_URL both set, it completes an MCP handshake and
    a select 1 through the launcher with POSTGRES_MCP_SANDBOX=1 POSTGRES_MCP_RUNTIME=oam, then checks under the launcher's own flags that
    the pinned port connects and the port next to it is refused with
    ERR_ACCESS_DENIED. The grant semantics oam 0.18.0 changed were measured by
    hand until now.
  • scripts/check-oam-floor.mjs, ported from aws-mcp. It checks that every
    floor claim in the launcher, the README and the suite names the same version
    as OAM_MIN (on every npm test, through src/oam-floor.test.ts), and,
    from release.sh, that the floor is not behind the latest oam release.
    POSTGRES_MCP_ALLOW_STALE_OAM=1 releases on an old floor deliberately; no
    network is not a failure.

v0.13.8

Choose a tag to compare

@jeffyaw jeffyaw released this 06 Oct 21:26
v0.13.8
7cb3ecd

Changed

  • The oam floor moves from 0.15.2 to 0.18.0, so the postgres-mcp launcher
    no longer runs the server on an older oam.
    This server is verified on one
    oam release at a time, and the floor keeps the launcher off anything older
    than that release. 0.18.0 is verified on the published
    aarch64-pc-windows-msvc binary, checksum matched against the release
    SHA256SUMS, through the launcher with OAM_BIN pointing at it and a local
    PostgreSQL 18.3: a full MCP handshake listing all 23 tools (the same 23 as on
    Node), a pg_readonly query answering byte-for-byte as it does on Node (same
    rows, same dataTypeName values), and the server process confirmed as that
    binary (oam run .../dist/index.js as the launcher's child). The sandbox the
    README documents was re-measured on it: with POSTGRES_MCP_SANDBOX=1 POSTGRES_MCP_RUNTIME=oam the launcher spawns oam --permission --allow-net=localhost:5432 --allow-env=... run, the same query answers as it
    does on Node, and under the same flags a connect to port 5433 and a file read
    are refused with ERR_ACCESS_DENIED. If the launcher finds only an oam from
    0.15.2 to 0.17.x, it now falls back to Node under the default
    POSTGRES_MCP_RUNTIME=auto, and exits with an error under
    POSTGRES_MCP_RUNTIME=oam or POSTGRES_MCP_SANDBOX=1
    -- it says so on
    stderr, naming the version it found and the floor (measured with oam 0.17.0:
    is oam 0.17.0, older than 0.18.0; using Node instead.). On Node every tool
    answers the same; what is lost is the sandbox, which needs a fresh oam at the
    floor and refuses to start without one. A host that launches the command with
    oam run on 0.15.2 to 0.17.x now hands off to a newer oam or to Node instead
    of serving in-process. A client that runs oam run /path/to/postgres-mcp/dist/index.js directly bypasses the launcher and keeps
    the oam it names. Run oam self-update, or set POSTGRES_MCP_RUNTIME=node to
    make the choice explicit.

Fixed

  • release.sh waits up to 600 s, not 300, for npm to serve a new version before
    the MCP Registry step, and polls npm up to 120 times 5 s apart in its final
    check, not five times. scripts/post-publish-smoke.sh tries its npm install
    of the new version up to 60 times, 10 s apart, instead of once. On 2026-09-29
    the @yawlabs/fetch-mcp 0.8.2 release spent 295 s of its 300 s gate waiting for
    npm to serve the new version, and npx needed 313 s, 24 of its 30 attempts,
    to install @yawlabs/lemonsqueezy-mcp 1.0.1. Release tooling only; the server
    itself is unchanged.
  • release.sh runs every mcp-publisher call to the MCP Registry, each login and
    each publish attempt, under coreutils timeout (MCP_PUBLISH_TIMEOUT_S,
    default 90 s) where one (or Homebrew's gtimeout) is on PATH -- without one
    the call runs unbounded, and a warning says so -- because mcp-publisher waits
    for the registry's answer with no limit of its own and a registry that never
    answered would have hung the release. A publish attempt that gets no answer is
    retried on the same 30, 60 and 90 s clock as the registry's own 429-504: one
    the limit stopped, which used to hang the step, and one whose connection
    failed or dropped (the client's error sending request or
    error reading response), which used to fail it at once. A login the limit
    stopped says the registry did not answer; one that failed any other way says
    how to read mcp-publisher's output (a 401 is the registry refusing the token
    exchange; a 429, a 5xx or a connection error is the registry or the network)
    instead of naming token scopes, which never fail a login: the registry reads
    the org role at login and refuses only at publish. A publish refused with a
    403 says what the io.github.YawLabs namespace takes: a YawLabs org Owner whose
    token can read org roles. Release tooling only; the server itself is
    unchanged.

v0.13.7

Choose a tag to compare

@jeffyaw jeffyaw released this 29 Sep 20:23
v0.13.7
1423eba

Fixed

  • release.sh reads npm's per-version document and retries the MCP Registry
    publish.
    Every "is x.y.z on npm?" check -- the step-5 skip, the CI hand-off
    poll, the npm propagation wait and the step-8 verify -- now goes through one
    helper that reads the per-version document the MCP Registry's validator
    fetches, not npm view's whole packument, which Cloudflare's edge can serve
    up to 5 minutes stale after a publish. A re-run whose npm publish still
    gets npm's E403 "cannot publish over the previously published versions"
    treats the version as published instead of failing with the token-error
    message. The MCP Registry publish is attempted up to four times, 30/60/90 s
    apart, but only on the answers waiting cures -- the version not found yet,
    "Likely transient, retry later", a failed npm metadata fetch, or the
    registry's own HTTP 429, 502, 503 or 504 -- with a fresh registry login
    before each retry; any other error still fails at once, and a
    duplicate-version answer counts as done.

Documentation

  • The README's Add to Yaw MCP button now sits directly under the title, and
    the follow badge links to @YawLabs.
    The one-click install button and its
    one-line caption moved up from further down the page, so they are the first
    thing under the name on npm and GitHub, and the X badge at the bottom points
    at @YawLabs instead of @TokenLimitNews. npm shows the
    README from the published package, which is why it takes a release to carry
    this there; the package's code is unchanged from 0.13.6.

v0.13.6

Choose a tag to compare

@jeffyaw jeffyaw released this 23 Sep 20:41
v0.13.6
977045c

Changed

  • fix(audit): reject directory paths at startup on Windows (#61)

v0.13.5

Choose a tag to compare

@jeffyaw jeffyaw released this 23 Sep 19:48
v0.13.5
938fb3d

Fixed

  • Agent SQL that never ran now leaves an audit line (#42). Whether a call
    that failed before its statement was sent wrote a line depended on which
    helper it went through. runInternal connects inside its audited step, so
    a refused connection wrote an internal line with ok: false and
    sqlstate: "ECONNREFUSED" for every tool that starts with a catalog query.
    The three runners behind agent SQL checked the connection out, opened the
    transaction and ran any hooks BEFORE the audited step, so pg_query,
    pg_readonly and pg_explain -- the tools the trail exists for -- wrote
    nothing, and so did pg_kill, whose checkout sat before its audit too.
    Measured by calling all 23 tools over stdio against
    postgres://u:p@127.0.0.1:1/x with POSTGRES_AUDIT_LOG=stderr (24 calls;
    pg_explain once plain and once with hypothetical_indexes): 13 lines,
    none of them user, and 11 calls with no line at all. An operator asking
    "what did the agent try to run during the outage" found nothing for
    exactly the tools that carry agent SQL. The same shape hid a failed
    BEGIN and a hook setup that threw: a pg_explain whose hypothetical
    index could not be created never ran its statement, and its only line was
    the HypoPG-installed check, with ok: true. The checkout, the BEGIN, the
    hook savepoint and the hypothetical-index setup now sit inside the
    statement's audited span, so whichever of them fails writes the
    statement's one user line: ok: false, the agent's sql (its keyword
    and hash under POSTGRES_AUDIT_REDACT), params as a count, the tool,
    and the code where there is one -- the Node code for a refused or
    unresolvable connection, none for a connect timeout or a missing
    DATABASE_URL, the SQLSTATE for a BEGIN or a setup the server refused
    (42P01 for a hypothetical index on a table that does not exist). Never
    two: the span is one wrapper and nothing inside it audits on its own, so a
    statement that is sent and fails writes exactly the line it always did.
    pg_kill's signal statement gets the same treatment. Review of the fix
    found two more silent paths with the same shape. pg_explain with a
    version-floored option (settings, wal, generic_plan, memory,
    serialize, buffers without analyze) probes server_version_num
    before composing its statement, the probe swallowed every failure into its
    "assume oldest" sentinel, and the gate then answered "the server version
    could not be determined ... drop the option and re-run" with no line
    written -- the wrong remediation for an outage, and the same unaudited
    probe is what six other tools ran first. The probe is now an internal
    line like the catalog SQL runInternal sends: once per successful
    reading, which is then cached for the life of the process (calls that
    race the first reading share one probe and one line), and on every probing
    call while the server is unreachable, since a failure is not cached. And
    the two gates that turn the sentinel into an error, pg_explain's and
    pg_io_stats's, now report the probe's own failure when it threw (connect ECONNREFUSED ..., DATABASE_URL is not set), keeping "could not be
    determined" for a server that answered something unparsable. The second
    path: the seven tools that run several catalog queries on one shared
    connection (pg_describe_table, pg_health, pg_advisor,
    pg_replication_status, pg_seq_scan_tables, pg_unused_indexes,
    pg_io_stats) checked that connection out before composing their first
    statement, so once the version was cached -- an outage that begins
    mid-session, the case the trail exists for -- a refused connection wrote
    nothing for any of them. withSharedClient now checks the connection out
    inside its first statement's audited step, so the refusal is that
    statement's ok: false line; statements waiting on the same checkout, or
    issued after it failed, fail with the same error and write nothing, and
    the failure still propagates as the exception every caller handles. The
    same 24 calls now write 30 lines (a tool that probes and then queries logs
    a failed probe and then its failed first statement), three of them user,
    and no call is silent; with the version cached beforehand, each of the
    seven writes its first statement's line. The README's "Audit logging"
    section says which line each tool writes for a refused connection -- for
    pg_explain that is the EXPLAIN itself unless a version-floored option
    or hypothetical_indexes was passed, when the probe's or the HypoPG
    check's line is the record and the EXPLAIN text is not on it. Two things
    follow for readers of the trail. ms on a user line (and on pg_kill's)
    now runs from the checkout to the statement's completion, so it includes
    waiting for a pooled connection and the transaction setup, as internal
    lines already could. And a refused connection comes back from
    runReadOnly, runReadWrite, runReadWriteRollback and pg_kill's
    handler as {ok: false} like every other failure, where it used to
    propagate as an exception; over MCP that is the same isError envelope,
    its message now ending in the code, (code: ECONNREFUSED). Tests drive
    each of the three runners against a pool whose connect() rejects and
    against a client whose BEGIN is refused, runReadOnly against a connect
    timeout, a missing DATABASE_URL and a hook setup that throws, pg_query
    and pg_readonly through the MCP wrapper for the tool tag, pg_kill
    against a rejecting connect(), pg_explain against a stubbed server that
    refuses hypopg_create_index and against a probe that fails, the probe
    itself for its shared, once-per-reading line, withSharedClient for the
    first statement's line under a fan-out, and each of the seven
    shared-connection tools with the version cached and the connection then
    refused; every fake client also records when it was released, so the
    restructured release paths are checked against their last statement.

v0.13.4

Choose a tag to compare

@jeffyaw jeffyaw released this 19 Sep 03:55
v0.13.4
d8c4007

Security

  • POSTGRES_MCP_SANDBOX no longer turns itself off without a word; when
    the sandbox cannot be applied, the launcher refuses to start (#41).
    Four
    paths left an operator believing they were sandboxed when they were not, and
    none of them printed anything.

    • The value. Only the exact value 1 enabled the sandbox, so true,
      1 and TRUE ran unsandboxed. It now reads like the audit variables:
      1 or true on, 0, false or off off, trimmed and case-insensitive.
      Any other value (yes, on) stops the launcher with a message naming the
      accepted ones. So does a value that is set but empty or only whitespace,
      as the audit variables have done since 0.13.3: that is what
      "POSTGRES_MCP_SANDBOX": "${SANDBOX}" becomes when the variable is unset
      where the MCP client runs, and reading it as unset ran the server
      unsandboxed for an operator who had asked for the sandbox. Only a variable
      that is not set at all counts as unset.
    • The runtime. POSTGRES_MCP_RUNTIME=node, no usable oam under auto,
      and an oam that failed to spawn all served without --permission -- in
      the launcher's own process, or handed off to Node -- with no message at all
      when no oam existed. Each now refuses, in every mode and on every host,
      with the fix named.
    • The network grant. An unset DATABASE_URL, a URL with no host
      (postgres:///db leaning on PGHOST, postgres://u@/db), a host list
      with ports, or anything new URL() rejected produced a bare --allow-net:
      every host on the internet. postgres://h1,h2/db produced
      --allow-net=h1,h2:5432, which oam reads as h1 on every port plus
      h2:5432. And ?host=, ?port=, PGPORT and percent-encoded hosts were
      ignored, which denied working setups their own database. The grant is now
      the one host:port the pg driver dials, resolved the driver's way --
      ?host=/?port=, then the URL, then PGHOST/PGPORT, then
      localhost:5432 -- and the suite checks it against the bundled pg on
      thousands of generated connection strings. With DATABASE_URL unset no
      --allow-net is passed at all, so every connection is denied and the
      server reports the missing variable as it does unsandboxed. A Unix socket,
      a host list, a port outside 1-65535, a host an exact grant cannot match, or
      an unreadable or non-postgres:// URL now refuses; DATABASE_URL is never
      printed.
    • Windows. An allowlisted variable spelled in another case (pghost,
      postgres_audit_log) reached the launcher, because Node reads names
      case-insensitively there, and was then stripped from the sandboxed server,
      because oam matches them exactly -- so a lower-case audit variable silently
      switched the audit trail off. Each is now passed in its exact spelling.

    Upgrading: this stops setups that only appeared to be sandboxed: the
    sandbox set with no usable oam under auto, with
    POSTGRES_MCP_RUNTIME=node, to a value such as yes, or to an empty value.
    Each now gets a message naming the fix; remove POSTGRES_MCP_SANDBOX or set
    it to 0 to keep running without it.

v0.13.3

Choose a tag to compare

@jeffyaw jeffyaw released this 19 Sep 03:04
v0.13.3
d7af7c8

Fixed

  • pg_kill now writes an audit line. The other tools audit their SQL
    through runUserQueryAudited, runInternal or withSharedClient.
    pg_kill uses none of them -- it needs the raw client, to capture the NOTICE
    that explains a false answer -- and queried that client directly, so with
    POSTGRES_AUDIT_LOG on, cancelling a query or terminating a backend left no
    line at all. Measured on PostgreSQL 17: one statement sent, zero lines
    written, and the cancelled statement's own 57014 line tied to nothing. It
    was the one tool whose whole purpose is to affect sessions other than its
    own. The signal call is now wrapped in place, on the same client, so NOTICE
    capture is unchanged. The line carries tool: "pg_kill" and
    source: "internal" (the server composes the statement; the agent supplies
    only the pid), and params: 1 -- the pid is a bound parameter, and the
    trail logs parameter counts, never values, with no exception carved out for
    a harmless one. ok reports whether the statement ran, so a
    signaled: false answer still reads ok: true. Checked on PostgreSQL 15,
    17 and 18: exactly one line for each cancel, terminate and unknown-pid call,
    with the NOTICE still in the tool's note and absent from the log. The
    README's "What the trail does not show" list is updated to match (#39).
    It also says what ok cannot tell you, and how to find the target's own
    line, the latter from 600 cancel and terminate calls measured over loopback
    TCP on PostgreSQL 15, 17 and 18. With the pid unlogged, ts is all that
    pairs the two lines: 81% of target lines carried the pg_kill line's
    millisecond or the next, but terminating a CPU-bound target took up to
    271 ms, and a statement timeout writes the same 57014 a cancel does.
    ok: true does not show that the target stopped: postgres answers true
    once the signal is sent, and on all three versions a session idle in a
    transaction ignored the cancel and carried on. And when the pid names the
    connection pg_kill itself runs on, its own line reads ok: false with
    57014 or 57P01 -- after a terminate, with that backend gone.
  • A cancel or a statement timeout that lands inside the row-cap DECLARE is
    no longer swallowed.
    Agent SQL runs through a cursor so the row cap is
    enforced by postgres, and a statement that cannot be a cursor (DDL, DML)
    fails at DECLARE and is run directly instead. That fallback fired on ANY
    DECLARE failure. DECLARE plans the query and locks every relation it
    reads, so a statement blocked on a table lock -- the "app is frozen" case
    pg_inspect_locks and pg_kill exist for -- is blocked inside it. Measured
    on PostgreSQL 15, 17 and 18, identically: cancelling such a pg_readonly
    call answered signaled: true, the backend went from DECLARE ... straight
    to the bare statement and blocked again, and when the lock was released the
    call returned rows, with ok: true in the audit log. A 3000 ms
    statement timeout fired, was swallowed, and the statement waited another
    3000 (6003 ms measured), holding its pooled connection for both. The
    second attempt ran outside the row cap, on the unbounded path the cursor
    exists to avoid. And a terminate had its 57P01 replaced by the error of a
    ROLLBACK TO sent down the dead socket, so the agent read "Connection
    terminated unexpectedly" and the audit line carried no sqlstate. The
    fallback now runs only for the two SQLSTATEs with which postgres says a
    statement cannot be a cursor: 42601 and 0A000. That set was measured,
    not assumed: 69 statement classes on each of the three versions, and every
    DECLARE failure outside it (42P01, 42703, 42883, 22012, 42P18,
    08P01) failed the same way on a direct run, so surfacing it at once
    changes no outcome. After the fix the cancel returns 57014 in under
    200 ms, the timeout fires once (3009 to 3027 ms), and the terminate reports
    57P01. Affects pg_readonly, pg_query and pg_explain.
  • pg_kill no longer takes the server down when its connection drops
    mid-call.
    0.13.2 gave every checked-out connection an error listener,
    because pg-pool removes its own while a client is out and node-pg's emit
    with no listener is an uncaught exception. "Every" missed one: pg_kill
    needs the raw client for its NOTICE listener and called
    getPool().connect() itself. With a connection dropped while its query was
    in flight the process exited, 7 of 7, and the tool call was never answered;
    the same drop under pg_readonly returned an error and kept serving.
    pg_kill now checks out through acquireClient() like everything else
    (7 of 7 survive, the error is returned, the next call succeeds), and a test
    fails if any source file checks a client out of the pool on its own (#57).
  • pg_kill says what a permission denial looks like. Its description,
    which is what an agent plans from, said a denied signal comes back as
    signaled: false with postgres's NOTICE in note. Postgres raises instead
    -- 42501, in every branch of signalfuncs.c on 15 through 18, and
    measured on 18 -- so the call returns an error and the target keeps
    running. The string the code quoted as that NOTICE was PostgreSQL 15's
    ERROR message. The description, the schema comments and the handler comment
    now say so, and say that signaled: true means the signal was sent, not
    that the target stopped. The fallback note for a bare false no longer
    lists a missing permission as a cause, which could send an agent looking
    for a grant that was never the problem (#56).
  • An empty POSTGRES_AUDIT_LOG or POSTGRES_AUDIT_REDACT stops the server
    at startup instead of counting as unset.
    An MCP client config entry such
    as "POSTGRES_AUDIT_REDACT": "${AUDIT_REDACT}", whose variable is unset
    where the client runs, can reach the server as present and empty -- and with
    auditing on, that logged full SQL for an operator who had asked for hashes,
    with no warning. An empty POSTGRES_AUDIT_LOG was read as unset too:
    auditing silently stayed off, or -- next to a POSTGRES_AUDIT_LOG_FILE --
    stayed on through the file, a guess either way. Both now refuse to start,
    whitespace-only values included, as POSTGRES_AUDIT_LOG_FILE already did;
    only a variable that is not set at all counts as unset. Upgrading: a
    config that sets either flag to an empty value started on 0.13.2 and will
    not start now. Remove the variable, or set it to 0 or 1 -- and next to a
    POSTGRES_AUDIT_LOG_FILE, remove it or set 1, since 0 with a file set
    is refused as before. The error for an unrecognized
    POSTGRES_AUDIT_REDACT now names the real risk, unredacted SQL, instead of
    a silently disabled audit trail (#40).