Skip to content

Releases: Idle-Sync/db-conn-mcp

v0.7.1

Choose a tag to compare

@github-actions github-actions released this 13 Aug 12:51

A security-hardening pass on the write-safety gate and the HTTP transport, from a full-repo
audit. Read the breaking changes first — the HTTP transport now requires a token.

Security fixes

  • A dry-run can no longer permanently commit via a stacked statement. Previously,
    execute_write_query(sql="DELETE FROM t; COMMIT;", dry_run=true) sent both commands over
    the driver's simple-query protocol; the embedded COMMIT ended the dry-run's wrapping
    transaction before it could roll back, so the change was committed for real while the tool
    reported rolled_back: true — bypassing the whole mode → dry-run → yolo → user_consent
    gate. The write path now rejects multi-statement SQL before executing.

  • Dry-run grants are scoped to the MCP session. A dry-run preview under one session no
    longer authorizes a commit of the identical statement from a different client. Under the
    http transport a single server process serves many clients; a grant made by one is now
    invisible to the others. stdio (one client per process) is unaffected.

  • The http/SSE transport now requires authentication. It previously served every tool,
    including execute_write_query, on 127.0.0.1:8000 with no credential — any local process
    could drive writes. Every request must now carry Authorization: Bearer <token>; the token
    is minted on first start, printed to the terminal, and persisted owner-only at
    ~/.db-conn-mcp/http-token. Requests without a valid token get 401; requests whose Host
    header isn't loopback get 403 (a DNS-rebinding defense). stdio is unaffected.

Breaking / Behaviour changes

  • --transport http clients must now send an auth token. After upgrading, an HTTP/SSE
    client that worked before will get 401 Unauthorized until you configure it with the
    header Authorization: Bearer <token>, where <token> is printed at server startup and
    stored at ~/.db-conn-mcp/http-token. Clients connecting from a non-loopback Host are
    refused with 403. stdio clients (the default) need no changes.

  • execute_write_query now rejects SQL containing more than one statement. If you relied
    on sending several ;-separated statements in one call, split them into separate calls;
    you will otherwise get a sanitized ValueError (Multiple SQL statements are not allowed; submit a single statement.). Semicolons inside string literals, dollar-quoted bodies, and
    comments are fine — only actual statement boundaries are rejected.

v0.7.0

Choose a tag to compare

@github-actions github-actions released this 12 Aug 14:07
2e6a441

The tools get sharper: bounds on everything unbounded, loud rejection of arguments that used
to vanish silently, raw database values fenced on every channel a client can read, and a
one-line nudge when a newer release exists. Still 23 tools and 2 prompts. Read the
breaking changes first
— three of them alter what existing consumers observe.

Breaking / Behaviour changes

  • The four tools that return raw row values no longer send a structuredContent
    channel.
    sample_table_rows, execute_read_query, fetch_rows, and search_value
    now answer on the text channel only — the same JSON as before, inside the
    <<<UNTRUSTED DATABASE DATA …>>> banner — and they no longer advertise an output
    schema. If you parsed structuredContent from any of these, read the text channel
    instead. A result with no rows is still an explicit answer: the banner contains an
    empty JSON list [], so "the table is empty" never arrives as an empty response. Why: that banner is the prompt-injection defence, and row values are the most
    attacker-controllable thing this server emits; a client that renders only the
    structured channel was seeing exactly that data with no fence around it. Every other
    tool (schemas, table stats, diagnostics, config) keeps its structuredContent
    unchanged.

  • check_sequences now returns only the problem sequences by default — pass
    behind_only=false for the full census.
    A database with 130 healthy sequences used
    to send all 130 rows back just to report the one that was stale; the default answer is
    now the stale ones alone, plus a new total_sequences field saying how many were
    checked, so behind_count: 0 still reads as an affirmative "all clear" rather than an
    empty result. behind_only=false restores exactly the previous list (also with
    total_sequences added). If you consumed sequences as a complete inventory of every
    sequence, pass behind_only=false.

  • A tool call carrying a parameter the tool doesn't have is now rejected instead of
    quietly ignored.
    Previously the unknown argument was dropped and the tool ran anyway
    — calling check_database(connection="prod") probed every configured database while
    looking like it had targeted one. Such a call now comes back as an error naming the
    unknown parameter(s), listing the ones the tool accepts, and suggesting the closest
    match for a near-miss spelling. Only parameter names appear in the message, never
    their values. If a client of yours passes extra arguments, it will now see errors where
    it previously saw (wrong) results — fix the argument name. Some clients also send stray
    or dummy arguments of their own, including to tools that take no parameters at all (for
    example list_databases); those calls now error too, and the error names the offending
    argument alongside the parameters the tool accepts.

Added

  • Interactive commands now tell you when a newer release is out. After a command
    like db-conn-mcp status or doctor finishes, a single line points at the newer
    version and how to upgrade. It only ever appears in a real terminal, is looked up in
    the background so it can never slow a command down (offline just means no line), and
    never changes the command's exit code. Set DB_CONN_MCP_NO_UPDATE_CHECK=1 to turn it
    off. The MCP server path never checks — a client launching db-conn-mcp makes no
    network calls of ours.

  • Three exploration tools can now be asked the narrower question directly.
    table_stats takes table (one table, optionally schema-qualified), min_size_bytes
    (skip anything smaller), and limit (top N by total size); list_tables takes
    pattern (fuzzy, case-insensitive name match, the same one find_columns uses) and
    limit; find_columns takes limit. All filtering happens in the database, so a huge
    schema no longer has to come back in full just to answer "which is the biggest table".
    When you pass limit to table_stats the result carries truncated: true/false;
    list_tables and find_columns still return a plain list of rows, simply a shorter
    one. Omit the new arguments and every one of these tools answers exactly as before.

  • explain_query now takes params, and get_table_schema can list a table's
    indexes.
    explain_query(sql, params=[…]) binds $1/$2 values through the driver
    exactly like execute_read_query, so you get the plan for the query you are actually
    about to run instead of having to rewrite it with literals — which can plan
    differently — and it composes with analyze=true. get_table_schema(..., include_indexes=true) adds an indexes list to the answer: each index's name, its key
    columns in index order (expression indexes report their expression; an INCLUDE payload
    is not a key column, so it is not listed), whether it is
    unique, and its method (btree, gin, …) — so "is this column indexed?" no longer needs a
    hand-written catalog query. Both are additive: without params the plan is exactly
    what it was, and without include_indexes the schema response has no indexes key at
    all.

Changed

  • doctor no longer says "you're up to date" beside a server process that isn't.
    After a pipx upgrade, a still-running client process keeps serving the old build —
    yet the release check happily reported v0.5.6 is the latest published version, which
    reads as an all-clear at exactly the wrong moment. When a pre-upgrade process is still
    running, that line now ends — but a running process predates this install; see process_staleness. Nothing changes when no stale process is found, and when psutil
    is missing (staleness is unknowable) the wording is untouched.

  • Doctor findings that tell you what to do now say it in suggested_action too.
    Failing checks used to ship suggested_action: "none" with the actual remedy buried in
    prose. Four sites now carry a machine-actionable verb: an unreachable database →
    fix_connection, a check that crashed → report_bug, the process check with no
    psutil installed → install_doctor_extra, and "no configuration found" →
    run_setup. If you match on the action vocabulary, add those four values; details and
    statuses are unchanged.

  • The tool descriptions your agent reads now state the size and the channel up front.
    Every tool whose answer grows with your database (list_tables, find_columns,
    table_stats, check_sequences) says so in its description and names the argument
    that bounds it, so an agent reaches for limit/pattern instead of pulling a whole
    schema back to filter it in context; and the four tools that return raw row values say
    outright that their rows come back on the text channel only, inside the untrusted-data
    banner. Descriptions only — no tool's behaviour changed because of this entry.

v0.6.2

Choose a tag to compare

@github-actions github-actions released this 12 Aug 10:54
568fb8e

The dashboard grows a front door and a face: a bare visit now tells you how in, a
bookmark keeps working, and the page looks like the instrument it is. Still 23 tools and
2 prompts — nothing about querying your databases changed.

Changed

  • The dashboard has been restyled as a bench instrument. It now reads like a piece of
    diagnostic equipment sitting next to your terminal rather than a web app: one column,
    engraved section labels, dense bordered rows instead of floating cards, quiet outlined
    buttons, and colour spent only on state. Every row carries a status lamp next to its
    title — hollow when idle, pulsing while an action is in flight, and green / amber / red
    once it settles — so you can see what your plumbing is doing without reading a word. The
    "your host process stopped" notice is now a mains-warning strip, and the whole page
    follows your system light/dark setting with contrast checked in both. Nothing you click
    behaves differently; only the appearance changed.
  • The "you need a token" page you get from visiting http://127.0.0.1:31415 by hand now
    looks like part of the tool
    instead of an unstyled browser default. It styles itself
    from a small inline block, because the real stylesheet sits behind the same guard that
    refused you — so that one response is served under a stricter policy than the rest of
    the dashboard (default-src 'none'; style-src 'unsafe-inline'): it may not fetch a
    script, image, font, frame or request of any kind, from anywhere. It still refuses you,
    still with a 403, and still discloses nothing but the tool's name and db-conn-mcp gui.

Added

  • Opening http://127.0.0.1:31415 without a token now tells you how to get in. Instead
    of a raw {"error": "forbidden"} with no way forward, a browser navigating to the
    dashboard gets a small page saying it needs a token and to run db-conn-mcp gui. The
    request is still refused (it is still a 403, and it still discloses nothing else) — only
    what a human sees changed. Every other unauthenticated request keeps the same opaque JSON
    refusal it always had.
  • The dashboard URL is now bookmarkable for the life of the server run. Opening the page
    with its token also sets a session cookie, so reloading the tab — or visiting the bare
    http://127.0.0.1:31415 after the ?token= is gone — keeps working instead of dropping
    you back to a 403. The cookie is HttpOnly and SameSite=Strict, it disappears when you
    close the browser, and it authorises reads only: anything that spawns a process,
    edits connections.json, or writes a client config still requires the real token. As
    before, restarting the server mints a new token and retires every existing session.
  • An empty Databases list now explains itself instead of rendering nothing: it points
    you at the Add form below, and says outright when adding one will create
    connections.json in your home directory.

v0.6.1

Choose a tag to compare

@github-actions github-actions released this 12 Aug 07:39
4efae26

One dashboard polish, found in first real-world use. No other changes.

Fixed

  • The dashboard no longer leaves you staring at frozen cards. When the host process
    stops (or the session expires), the notice explaining it now scrolls itself into view
    instead of sitting off screen at the top of the page, and every action it interrupted is
    released: stuck connecting... / verifying... labels are replaced with
    stopped - see the notice at the top of the page, and the buttons they disabled become
    clickable again.

v0.6.0

Choose a tag to compare

@github-actions github-actions released this 12 Aug 07:07
4c3fbe6

A browser dashboard, and the first proof your setup actually speaks MCP. Still 23 tools and
2 prompts — nothing about querying your databases changed, but read the behaviour changes
below: the server now hosts the dashboard by default.

Added

  • A browser dashboard — the CLI's equal, clickable. One page on
    http://127.0.0.1:31415, in three sections:

    • Databases — add, edit, remove and test your connections without hand-editing
      connections.json. A stored DSN is never shown, by anything, ever: the field is
      write-only, so an edit form starts blank and leaving it blank keeps the DSN already
      saved. A connection's name is fixed once created (to rename, remove and re-add), and
      fallback ports can be set or changed from here. A name that is blank, padded with
      spaces, or contains a / is refused: the dashboard could save one, but could never
      edit, test or remove it again.
    • Clients — the same nine MCP clients the wizard knows, each with inject/uninject
      buttons and the exact command and arguments that client would launch. A client
      whose config file cannot be parsed is listed and explained, never written to — the same
      refusal setup and clients make.
    • Verify & Doctor — the live verification below, plus the full doctor sweep with the
      same ok / warn / fail / skipped findings the CLI prints.
  • Live MCP verification — "does the binary my client launches actually answer?" For each
    detected client, the dashboard spawns the exact command and arguments stored in that
    client's own config and holds a real MCP conversation with it using the SDK's own client
    library: initialize, then tools/list (23 expected), then a real list_databases call.
    The verdict is one of answers, launch_failed, handshake_failed, wrong_tool_count,
    timeout — evidence, not a guess. The dashboard never answers from its own process, so a
    client pointed at a different install is caught rather than masked. One more button runs
    the same check over the HTTP (SSE) transport (port_in_use when port 8000 is already
    taken).

  • Stale-install detection. When the server a client launches reports a different version
    than the dashboard itself is running, the result is flagged as stale with the upgrade
    command — the "you upgraded but that client still starts the old copy" case, now visible
    per client rather than inferred.

  • db-conn-mcp gui opens the dashboard: it reuses the one a running server is already
    hosting, or starts a standalone one (which shuts itself down after 15 idle minutes) and
    opens your browser at it. db-conn-mcp setup now ends with a tip pointing at the command,
    so a first-time user discovers the dashboard instead of never hearing about it.

  • A dashboard tab left open from before a restart says so. Each start mints a fresh
    token, so an old tab's requests are refused; the page now shows a single banner asking
    you to run db-conn-mcp gui again, instead of every panel failing for no stated reason.

Breaking / Behaviour changes

  • Starting the MCP server now also starts a local dashboard listener on
    127.0.0.1:31415.
    Every server start hosts the dashboard alongside the MCP protocol;
    the first server process to start wins the port and the rest skip it silently, so several
    clients running the server at once still means exactly one dashboard. It listens on
    loopback only — no connection from another machine can reach it — and every request,
    including the page itself, must carry a secret token generated at start-up and stored in a
    user-only file at ~/.db-conn-mcp/gui-token. To turn it off, add --no-gui to the
    db-conn-mcp command in your client's config. If port 31415 is already taken by something
    else on your machine, the server simply carries on without a dashboard.

  • The server now reports its own version to your MCP client. During the initialize
    handshake, serverInfo.version used to be the version of the underlying MCP SDK (e.g.
    1.27.2) because the SDK fills that in when a server does not supply one. It is now the
    db-conn-mcp version (e.g. 0.5.6). If you script against that field, expect our version
    there from now on — and it finally lets a client tell which build of db-conn-mcp it is
    talking to.

Changed

  • Three dependencies are now declared explicitly: starlette, uvicorn and
    httpx. All three already arrived with mcp, but the dashboard imports them
    directly, and an import of ours must not rest on somebody else's transitive pin.
  • db_conn_mcp.server.run() takes a new gui=True keyword (the CLI passes not --no-gui).
    Calling it as before is unchanged apart from the listener described above.

v0.5.6

Choose a tag to compare

@github-actions github-actions released this 11 Aug 11:34
fe435db

Codex joins the setup targets, and no client config you have is overwritten any more when it
cannot be read. Still 23 tools and 2 prompts — nothing about querying your databases changed.

Added

  • Codex is now a setup target. db-conn-mcp setup and db-conn-mcp clients detect
    ~/.codex/config.toml (or $CODEX_HOME/config.toml) and write a [mcp_servers.db-conn-mcp]
    entry, bringing the auto-injection list to nine clients. One entry covers the ChatGPT
    desktop app, the Codex CLI and the IDE extension, which share that file. Your comments,
    formatting and other MCP servers in that file are preserved. The entry carries an explicit
    startup_timeout_sec = 30, because Codex's 10s default can be tight for Python startup on
    a cold disk.

  • doctor now flags a client config it cannot read. A detected MCP client whose config
    file does not parse gets a client_paths warning (repair_client_config) telling you to fix
    that file by hand and re-run db-conn-mcp clients. Previously clients and status both
    showed the problem while doctor — the one command whose whole job is diagnostics — stayed
    silent about it. The finding names the client and the path, never the file's contents.

Fixed

  • A client config that is not valid UTF-8 no longer crashes db-conn-mcp status. Reading a
    client's config used to guard against a bad-JSON or an I/O error but not against undecodable
    bytes, so a config saved in a non-UTF-8 encoding took the whole command down with a traceback.
    Such a file is now treated like any other unreadable config: the client is listed as
    config unreadable and left untouched. doctor and the injection commands were affected the
    same way.

Breaking / Behaviour changes

  • A client config file that exists but does not parse is no longer overwritten. Previously,
    if setup or clients could not read a client's config, it treated the file as empty and
    wrote a fresh one — silently discarding whatever was in there, including your other MCP
    servers. It now skips that client, tells you which file it could not parse, and leaves the
    file untouched. The client still appears in every listing — setup, status and
    clients --remove — marked config unreadable, so you can fix it by hand and re-run.
    clients --remove reports it without offering it as a removal target (uninjecting means
    rewriting the file, which is exactly what we refuse to do), so it no longer answers a broken
    config with a bare "not injected into any detected MCP client". "Could not read" covers a
    syntax error, an unreadable file, and a file whose top level is not an object. This affects
    all nine clients, not just Codex.

Changed

  • Adds one dependency, tomlkit (pure Python, no transitive dependencies). Codex's config
    is TOML, and the standard library has a TOML reader but no writer at any Python version.

v0.5.5

Choose a tag to compare

@github-actions github-actions released this 10 Aug 13:04
fa30bdb

Documentation and packaging only. No code change, no behaviour change — still 23 tools and
2 prompts, and nothing about how the server runs is different from 0.5.4.

Added

  • This changelog, covering all releases back to 0.1.0. Entries for 0.5.2 and earlier were
    backfilled from each release's own contemporaneous notes.
  • A Changelog link on the PyPI project page, via a Changelog project URL.

Changed

  • Release notes now come from this file. The release workflow extracts the tag's section and
    passes it to gh release create --notes-file, so GitHub, PyPI and the repo cannot drift
    apart. A missing section logs a warning and falls back to generated notes, so a release can
    never fail for want of prose.

    This replaces --generate-notes, which emitted PR-title lists. That was how 0.5.3 shipped a
    breaking change to tool output described only as "Fence tool output as untrusted database
    data" — accurate about the work, silent about the consequence.

  • The notes for 0.5.2, 0.5.3 and 0.5.4 have been rewritten from their changelog entries.
    0.1.0 through 0.5.1 keep their original hand-written notes, which were already better than
    anything a regeneration would produce.

  • Rule 7 (living documentation) now covers CHANGELOG.md, so entries are written with the
    change rather than remembered afterwards.

v0.5.4

Choose a tag to compare

@github-actions github-actions released this 10 Aug 11:56
66031b5

Infrastructure only. No tool was added, removed or changed — still 23 tools and 2 prompts.

Breaking / Behaviour changes

  • Python 3.10 and 3.11 are no longer supported. The floor is now 3.12. Python 3.10
    reaches end of life on 2026-10-31 and 3.11 is already security-only. If you are on 3.10 or
    3.11, pip install --upgrade db-conn-mcp will keep you on 0.5.3 rather than fail — pip
    honours requires-python — so the effect is silent: you simply stop receiving updates.
    Install on 3.12+ to continue.

    Note this shipped as a patch bump. Under semver, dropping interpreters argues for a minor.

Added

  • .github/workflows/ci.yml — lint and the full test suite on every push to main and every
    pull request: Python 3.12, 3.13 and 3.14 on Linux, plus the floor version on Windows and
    macOS (client_specs() branches three ways on sys.platform). Previously publish.yml was
    the only workflow, it runs on tags only, and it never ran pytest or ruff — so nothing had
    ever been gated on the tests. The green "Analyze (python)" check on pull requests is GitHub's
    default CodeQL setup, not this project's suite.
  • Python 3.13 and 3.14 classifiers. Both were missing, so the package did not advertise support
    for either actively-maintained branch.

Changed

  • ruff is pinned exactly (ruff==0.16.2) in the dev extra rather than floored, and CI
    installs it from that extra so pyproject.toml stays the single source of truth. A floor let
    CI resolve a newer ruff than a developer had locally and fail on rules they could not
    reproduce.
  • Markdown is excluded from ruff. Ruff 0.16+ formats Python blocks inside .md, and doc
    snippets here are illustrative while docs/superpowers/ holds dated artifacts that must stay
    verbatim.
  • Internal modernisation unlocked by the new floor: asyncio.TimeoutErrorTimeoutError
    (the same object since 3.11) and timezone.utcdatetime.UTC. No behaviour change.

Full Changelog: v0.5.3...v0.5.4

v0.5.3

Choose a tag to compare

@github-actions github-actions released this 10 Aug 10:53
50ec7fc

Breaking / Behaviour changes

  • A tool result's text content block is no longer bare JSON. Every result is now fenced
    between <<<UNTRUSTED DATABASE DATA …>>> markers, so json.loads(result.content[0].text)
    will raise.

    structuredContent is unchanged and byte-identical, so clients that read it — the
    spec-conforming path, and what all 23 tools declare an output schema for — are unaffected.
    Only code parsing the text channel needs updating. A list[dict] tool emits one text block
    per item and each is fenced individually.

  • A failed commit now consumes its dry-run grant. Previously the grant survived, letting
    an agent retry the identical statement immediately. Because a commit can fail ambiguously —
    a statement timeout, or a connection dropped during COMMIT, may still have applied the
    write server-side — that retry could silently double-apply a non-idempotent statement such as
    UPDATE t SET n = n + 1. One preview now authorises exactly one commit attempt, so a failed
    commit requires a fresh dry-run that shows the agent the current state before it retries.

    This reverses a deliberate earlier choice that favoured retry ergonomics.

Added

  • Prompt-injection hardening (guard.py). Database content is attacker-controllable: anyone
    who can insert a row, or name a table or column, chooses text that lands verbatim in the
    agent's context. Two layers, both advisory to the model:

    • Every tool result's text channel is fenced with an explicit "this is data, not
      instructions" notice, applied at a single seam (GuardedFastMCP.call_tool) rather than in
      each tool.
    • A standing policy travels in the server's initialize instructions — the durable half,
      since a client reading only structuredContent never sees the per-response fence.

    Markers found inside a payload are defanged first, so hostile content cannot close the fence
    early and appear to speak with the server's authority.

    This is mitigation, not a guarantee: a determined injection can still influence a model.

Changed

  • Both raw-SQL tool descriptions now tell the agent to confirm table and column names with
    get_table_schema (or list_tables / find_columns) when they are not already in context,
    and explicitly not to re-fetch a schema it already has. Guidance only — nothing is enforced
    server-side and no call is mandatory.

Fixed

  • The doctor MCP tool re-checks the config file's existence per call, as the CLI already did.
    A config deleted or made unreadable after the server started produced a hard fail reading
    "connections.json exists but could not be read" instead of the intended skip, "no
    configuration found — run db-conn-mcp setup".

Full Changelog: v0.5.2...v0.5.3

v0.5.2

Choose a tag to compare

@github-actions github-actions released this 10 Aug 07:55
72b62aa

Closes #12. Two features: whole-setup diagnostics, and making the write preview mandatory.

Breaking / Behaviour changes

  • execute_write_query now defaults to dry_run=true, and the preview is enforced rather
    than advisory.
    A commit (dry_run=false) is rejected unless the identical statement was
    dry-run first. A bare call therefore previews instead of committing — an agent that used to
    call the tool and have it write will now get a preview back.

    The grant fingerprints database + SQL + params with a 10-minute TTL. skip_dry_run=true
    exists solely for an agent to attest the user explicitly asked to skip the preview.

    Gate order is now mode → dry-run-first → yolouser_consent. yolo cannot skip the
    preview
    , and nothing can ever make a read database writable.

Added

  • doctor — whole-setup diagnostics, not just connectivity. One engine (doctor.py), two
    surfaces: db-conn-mcp doctor [--offline] (exit 0 only if nothing fails, 2 otherwise) and a
    doctor MCP tool returning {check, status, detail, suggested_action} so an agent can
    self-diagnose mid-session. 23 tools total.

    Six checks, each drawn from a real failure during the 0.5.0 → 0.5.1 upgrade: running server
    processes older than the installed package (psutil optional, and it reports its own host
    process when stale); a cache-bypassed PyPI version check; per-database connectivity plus a
    credential-free listener probe of fallback ports, catching "a different local Postgres
    answered my port"; config-schema typos with did-you-mean hints; secrets exposure (POSIX file
    mode, git-committability); and injected client entries whose command path no longer exists.

    The engine never raises — a crashing check degrades to a fail naming only the exception
    type. A poisoned-DSN sweep test asserts no DSN, host, user or password can reach any finding,
    including via pydantic validation errors (Rule 6).

  • check_database UNREACHABLE results now report failed_port.

Changed

  • MCP-client helpers moved from cli.py into a new clients.py (re-exported for
    compatibility), unlocking reuse without a circular import.

Full Changelog: v0.5.1...v0.5.2