Skip to content

v0.30.1: keboola-mcp-server auto-update banner re-fires on every kbagent invocation #263

Description

@ottomansky

Status update (2026-05-07): v0.30.2 (release, PR #262) fixes Bug A — the local-version probe now reads uv tool list instead of running keboola_mcp_server --version. The user-visible banner is gone for healthy installs because the probe now returns a real version → _is_up_to_date short-circuits as True → bugs B / C / D never fire.

Bugs B, C, D remain latent. @padak is working on the remaining three. They are masked today by the bug-A fix but will reappear if any of these conditions hold: (i) MCP server is genuinely behind PyPI (Bug B fires the broken uvx --version upgrade subprocess), (ii) probe still returns None on some install path we haven't tested (Bug C falls through to the upgrade attempt), (iii) any code path triggers maybe_auto_update() more than once per process (Bug D amplifies). Tracking continues here until all four are addressed.

Symptom

After upgrade to v0.30.1, every kbagent command (and every nested command
inside kbagent repl) prints:

Updating keboola-mcp-server vunknown -> v1.59.1 (via uvx)...
keboola-mcp-server upgrade skipped: ... AuthlibDeprecationWarning ...
usage: python -m keboola-mcp-server [-h] ...
python -m keboola-mcp-server: error: unrecognized arguments: --version;
continuing with current version.

The banner fires even after kbagent doctor --fix runs uv tool install keboola-mcp-server and doctor reports MCP PASS (MCP server available via: keboola_mcp_server (transport=stdio)).

Edit (corrected 2026-05-07): the original issue body framed bug #2
as "vunknown gets cached and never satisfies up-to-date". After
reading the actual v0.30.1 source, that framing is wrong — vunknown
is just a format-string artifact (f"v{local_version or 'unknown'}"
in auto_update.py:_maybe_update_mcp), nothing is poisoned in the
cache. The real chain is four bugs, not three; corrected analysis
below.

Root cause (four chained bugs)

Code references against padak/keboola_agent_cli main @ commit
5e90febd9 (v0.30.1 tip).

Bug A — local-version probe always returns None for non-pip-env installs ✅ FIXED in v0.30.2 (#262)

services/version_service.py:_get_local_mcp_version (lines ~41-83) tried:

  1. subprocess.run([keboola_mcp_server, "--version"], ...) — the
    keboola-mcp-server binary uses argparse without a --version flag.
    It exits with returncode=2 and prints error: unrecognized arguments: --version. Probe returncode check fails → no version captured.
  2. Fallback: importlib.metadata.version("keboola-mcp-server") — only
    succeeds when MCP is pip-installed in the active Python environment.
    Fails for uv tool install (different venv) and uvx (cache only).
  3. → returned None.

For practically every real install path (the one kbagent doctor --fix
itself produces, plus uvx-cache-only), the probe returned None.

Fix in v0.30.2: probe now reads uv tool list first (canonical for
uv tool install-managed binaries) and falls back to importlib.metadata
for pip-env installs; the --version subprocess is kept as a final
fallback for the day MCP adds the flag.

Bug B — uvx upgrade subprocess uses the same broken --version arg ⏳ PENDING

services/version_service.py:_perform_mcp_update lines ~206:

elif method == "uvx":
    cmd = [uvx_path, "--refresh", "--from", MCP_PACKAGE_NAME,
           MCP_BINARY_NAME, "--version"]

uvx --refresh does refresh the cache, but the trailing --version
no-op probe is rejected by the binary the same way as bug A. Subprocess
returncode≠0 → upgrade reported as failed in the user-facing banner —
even when the refresh itself succeeded.

Why latent today: masked by the Bug A fix in v0.30.2. With a working
probe, healthy installs short-circuit on up_to_date is True before this
branch runs. The bug surfaces if a uvx-cache install IS genuinely behind
PyPI and triggers the upgrade subprocess.

Bug C — probe-None falls through to the upgrade attempt every fresh-cache pass ⏳ PENDING

auto_update.py:_maybe_update_mcp (~line 270, the if up_to_date is True: return short-circuit):

local_version = _get_local_mcp_version()                  # may still be None
up_to_date = _is_up_to_date(local_version, mcp_latest)    # = None  (not True)
if up_to_date is True:                                    # not taken
    return mcp_latest

method = _detect_mcp_install_method()
if method == "none":
    return mcp_latest

sys.stderr.write(f"Updating keboola-mcp-server v{local_version or 'unknown'} ...")
success, info = _perform_mcp_update(method=method, ...)   # fires (Bug B)

up_to_date is None, not True, so the function falls through to the
banner + broken upgrade subprocess. There is no "local version is
undetectable, skip the upgrade attempt" gate.

Why latent today: Bug A fix usually returns a real version on the
canonical install path. The bug surfaces on any install path the new
probe still can't read (e.g. pure uvx-cache without uv tool registration,
or a future install method).

Bug D — kbagent repl re-runs maybe_auto_update() on every prompt iteration ⏳ PENDING

commands/repl.py:_run_repl (~line 181):

click_app(full_argv, standalone_mode=False)

Each REPL iteration re-enters the entire CLI through Click. cli.py:243
unconditionally calls maybe_auto_update() from the main() callback, so
the auto-update flow fires once per command typed at the prompt. Combined
with bugs A+B+C, the user saw the broken-banner output once per prompt.

Why latent today: with Bug A fixed, each per-iteration maybe_auto_update()
call short-circuits as up-to-date and prints nothing. The bug becomes
visible again on any combination that triggers a non-no-op auto-update
flow inside the REPL — e.g. genuine update available, network flakes, or
any of B/C re-emerging.

Repro

uv tool upgrade keboola-agent-cli   # to v0.30.1 (NOT v0.30.2)
kbagent repl
> project list
> project list   # banner re-fires every time

Same noise also appeared outside the REPL on every individual kbagent
invocation, but the REPL made the per-iteration amplification (bug D)
visually obvious. Cannot be reproduced on v0.30.2+ without forcing one
of the latent paths.

Suggested fixes (B / C / D — A landed in v0.30.2)

  • Bug B: for method == "uvx", the upgrade should promote the
    uvx-cache install to a uv tool install (matching what kbagent doctor --fix already does). uv tool install --upgrade keboola-mcp-server persists the binary on PATH and exits 0 cleanly.
    Minimal alternative: change --version to --help (argparse handles
    --help natively → exits 0), but that leaves the uvx-only model in
    place.
  • Bug C: when _get_local_mcp_version() returns None, skip the
    upgrade attempt for this TTL window. Cache TTL still holds, so the
    next fresh-cache pass will retry.
  • Bug D: add a process-level sentinel
    (_AUTO_UPDATE_RAN: bool = False at module scope of auto_update.py).
    First call sets True; subsequent in-process calls short-circuit. Re-
    exec'd processes start with a fresh sentinel so the kbagent self-
    upgrade → re-exec → MCP-stage chain from PR feat(0.30.1): auto-update keboola-mcp-server on startup, parity with kbagent (closes #243) #257 is preserved.

Environment

  • kbagent v0.30.1 (auto-upgraded from v0.27.0)
  • keboola-mcp-server v1.59.1 (installed via uv tool install from
    kbagent doctor --fix)
  • macOS Darwin 25.4.0, zsh
  • uv tool list | grep keboola-mcp-serverkeboola-mcp-server v1.59.1
    (the install was valid — the v0.30.1 probe was wrong)

Acceptance criteria

  • kbagent project list prints zero auto-update noise on a healthy
    install — resolved by v0.30.2 (Bug A)
  • kbagent repl runs the auto-update check at most once per session
    (Bug D)
  • Probe-None does not fall through to the upgrade attempt (Bug C)
  • uvx-cache installs upgrade cleanly (or are promoted to
    uv tool install so subsequent runs use the persistent path) (Bug B)
  • Regression test mocks _get_local_mcp_version() -> None and
    asserts _perform_mcp_update is NOT called (Bug C)
  • Regression test asserts a second invocation within the same REPL
    session does not re-trigger maybe_auto_update() (Bug D)

Local workaround (no longer needed on v0.30.2+)

For anyone still pinned to v0.30.1, the noise can be silenced via:

export KBAGENT_AUTO_UPDATE=false

Honored at auto_update.py:_should_skip_all; bypasses the entire
auto-update flow. Run kbagent update manually to upgrade. Once on
v0.30.2 this workaround is unnecessary.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions