From 9507c4b0a193fc538cc924ae7c8fb370605ee5f6 Mon Sep 17 00:00:00 2001 From: Martin Vogel Date: Sat, 6 Jun 2026 16:47:34 +0200 Subject: [PATCH 1/4] Add official MCP Registry publishing to the release pipeline - Add `mcpName` to the npm package and an `mcp-name:` marker to the PyPI README so the registry can verify package ownership for both. - Rewrite server.json to reference the npm + PyPI packages instead of five per-platform mcpb tarballs. The old form was pinned to a stale 0.6.1 with hardcoded SHA-256s that needed manual updates every release; the package form only needs a version sync. Also trims the description to the registry's 100-character limit. - Add a publish-mcp-registry job: downloads mcp-publisher, authenticates via GitHub Actions OIDC (no token, no device flow), syncs server.json's version to the just-published npm package, and publishes. It runs after npm/PyPI but does not gate the release un-draft, so a registry outage never blocks shipping and the job can be retried on its own. --- .github/workflows/release.yml | 38 +++++++++++++++++++++++++++++++ pkg/npm/package.json | 1 + pkg/pypi/README.md | 2 ++ server.json | 43 +++++++++-------------------------- 4 files changed, 52 insertions(+), 32 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a792d096a..9c19e0246 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -308,6 +308,44 @@ jobs: TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }} run: twine upload --non-interactive dist/* + # ── 8b. Publish server.json to the official MCP Registry ────── + # Runs after npm + PyPI so the registry can verify package ownership + # (npm: `mcpName` field in package.json; PyPI: `mcp-name:` marker in + # the package README). Authenticates with GitHub Actions OIDC — no + # token, no interactive device flow. server.json's version is synced + # from the just-published npm package so it always matches the release. + # + # Intentionally does NOT gate publish-final: the binary release is the + # product, the registry entry is metadata. A registry-preview outage + # must never block shipping. Re-run this job alone to retry — it does + # not touch npm/PyPI, so retries are safe. + publish-mcp-registry: + needs: [publish-registries] + runs-on: ubuntu-latest + permissions: + id-token: write # GitHub OIDC auth to the MCP Registry + contents: read + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Sync server.json version to the released package + run: | + VERSION=$(jq -r '.version' pkg/npm/package.json) + jq --arg v "$VERSION" '.version = $v | (.packages[].version) = $v' \ + server.json > server.tmp && mv server.tmp server.json + echo "server.json pinned to $VERSION" + cat server.json + + - name: Install mcp-publisher + run: | + curl -fsSL "https://github.com/modelcontextprotocol/registry/releases/latest/download/mcp-publisher_$(uname -s | tr '[:upper:]' '[:lower:]')_$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/').tar.gz" | tar xz mcp-publisher + + - name: Authenticate to MCP Registry (GitHub OIDC) + run: ./mcp-publisher login github-oidc + + - name: Publish server.json to MCP Registry + run: ./mcp-publisher publish + # ── 9. Atomic un-draft (only after npm + PyPI succeed) ──────── # The GitHub release stays in DRAFT until both registries publish. # If anything upstream fails, the draft can be deleted and the run diff --git a/pkg/npm/package.json b/pkg/npm/package.json index cd2b0d644..9ca1a51f3 100644 --- a/pkg/npm/package.json +++ b/pkg/npm/package.json @@ -2,6 +2,7 @@ "name": "codebase-memory-mcp", "version": "0.7.0", "description": "Fast code intelligence engine for AI coding agents — single static binary MCP server", + "mcpName": "io.github.DeusData/codebase-memory-mcp", "license": "MIT", "repository": { "type": "git", diff --git a/pkg/pypi/README.md b/pkg/pypi/README.md index eff394868..97d09ccaf 100644 --- a/pkg/pypi/README.md +++ b/pkg/pypi/README.md @@ -1,5 +1,7 @@ # codebase-memory-mcp +mcp-name: io.github.DeusData/codebase-memory-mcp + **Fast code intelligence engine for AI coding agents.** Indexes an average repository in milliseconds, the Linux kernel (28M LOC) in 3 minutes. Answers structural queries in under 1ms. This package installs the `codebase-memory-mcp` binary from [GitHub Releases](https://github.com/DeusData/codebase-memory-mcp/releases). The binary is downloaded on first run and cached in your OS cache directory. diff --git a/server.json b/server.json index 9691a0c51..122dfbeab 100644 --- a/server.json +++ b/server.json @@ -2,49 +2,28 @@ "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", "name": "io.github.DeusData/codebase-memory-mcp", "title": "Codebase Memory", - "description": "Persistent codebase knowledge graph — 159 languages, sub-ms queries, 99% fewer tokens than grep. Survives session restarts and context compaction.", + "description": "Codebase knowledge graph for AI agents — 159 languages, sub-ms queries, 99% fewer tokens.", "repository": { "url": "https://github.com/DeusData/codebase-memory-mcp", "source": "github" }, - "version": "0.6.1", + "websiteUrl": "https://deusdata.github.io/codebase-memory-mcp/", + "version": "0.7.0", "packages": [ { - "registryType": "mcpb", - "identifier": "https://github.com/DeusData/codebase-memory-mcp/releases/download/v0.6.1/codebase-memory-mcp-darwin-arm64.tar.gz", - "fileSha256": "3468e5df340e53dba2d23cc30bf44210c412dfb2538d7da992465934bf4980e0", + "registryType": "npm", + "identifier": "codebase-memory-mcp", + "version": "0.7.0", + "runtimeHint": "npx", "transport": { "type": "stdio" } }, { - "registryType": "mcpb", - "identifier": "https://github.com/DeusData/codebase-memory-mcp/releases/download/v0.6.1/codebase-memory-mcp-darwin-amd64.tar.gz", - "fileSha256": "a85f8313181c7ffd3a0ac494c0e2bf1b5647f15de06686f8a0728162bf4326ac", - "transport": { - "type": "stdio" - } - }, - { - "registryType": "mcpb", - "identifier": "https://github.com/DeusData/codebase-memory-mcp/releases/download/v0.6.1/codebase-memory-mcp-linux-amd64.tar.gz", - "fileSha256": "a24c04fd1fd61b08158e011685cf08d860e3594e4ce057ffda57d1cc2f4afdd1", - "transport": { - "type": "stdio" - } - }, - { - "registryType": "mcpb", - "identifier": "https://github.com/DeusData/codebase-memory-mcp/releases/download/v0.6.1/codebase-memory-mcp-linux-arm64.tar.gz", - "fileSha256": "29811aadc2aa99fb9612683af30a06d03fcb2bfefb95c12ea672671827bd111c", - "transport": { - "type": "stdio" - } - }, - { - "registryType": "mcpb", - "identifier": "https://github.com/DeusData/codebase-memory-mcp/releases/download/v0.6.1/codebase-memory-mcp-windows-amd64.zip", - "fileSha256": "e331f84f2ec760d179025b16b440b0f14282e83f2432318969dc69dde8ffffd0", + "registryType": "pypi", + "identifier": "codebase-memory-mcp", + "version": "0.7.0", + "runtimeHint": "uvx", "transport": { "type": "stdio" } From fa1021630a37ce7a15fe94dd2da134a0c1a7f1e5 Mon Sep 17 00:00:00 2001 From: Martin Vogel Date: Tue, 9 Jun 2026 10:46:05 +0200 Subject: [PATCH 2/4] Add Glama directory integration (glama.json + check image) - glama.json claims maintainership on glama.ai. - packaging/glama/Dockerfile wraps the static Linux binary in a minimal image so Glama can launch the stdio server and run its MCP introspection checks (which power the directory score badge). The tool needs no Docker to run; this image is only for the directory integration. Fetches the latest release binary, arch-aware via TARGETARCH. - packaging/glama/verify.sh builds the image and asserts the server answers initialize + tools/list with no project indexed. - _smoke.yml runs verify.sh as a non-gating job (continue-on-error) so the integration is guarded against drift without ever blocking a release. --- .github/workflows/_smoke.yml | 19 ++++++++++++++++++ glama.json | 4 ++++ packaging/glama/Dockerfile | 25 ++++++++++++++++++++++++ packaging/glama/verify.sh | 38 ++++++++++++++++++++++++++++++++++++ 4 files changed, 86 insertions(+) create mode 100644 glama.json create mode 100644 packaging/glama/Dockerfile create mode 100755 packaging/glama/verify.sh diff --git a/.github/workflows/_smoke.yml b/.github/workflows/_smoke.yml index d2429db4e..75e4506fb 100644 --- a/.github/workflows/_smoke.yml +++ b/.github/workflows/_smoke.yml @@ -194,3 +194,22 @@ jobs: scripts/security-strings.sh ./codebase-memory-mcp scripts/security-install.sh ./codebase-memory-mcp scripts/security-network.sh ./codebase-memory-mcp + + # ── Glama directory check image (non-gating) ────────────────────── + # Builds packaging/glama/Dockerfile — the image Glama builds to score the + # server — and runs an MCP initialize + tools/list handshake to confirm the + # containerized stdio server still starts and introspects. Guards the Glama + # listing integration against drift (Dockerfile breakage, release-asset + # renames, introspection regressions). + # + # continue-on-error: a broken *directory* image must never block a release — + # the shipped product binaries are unaffected. The red X is the signal to fix + # the Glama integration, not a release gate. + smoke-glama-image: + runs-on: ubuntu-latest + timeout-minutes: 10 + continue-on-error: true + steps: + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + - name: Build Glama image and verify MCP introspection + run: bash packaging/glama/verify.sh diff --git a/glama.json b/glama.json new file mode 100644 index 000000000..a8304bca0 --- /dev/null +++ b/glama.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://glama.ai/mcp/schemas/server.json", + "maintainers": ["DeusData"] +} diff --git a/packaging/glama/Dockerfile b/packaging/glama/Dockerfile new file mode 100644 index 000000000..52d364a72 --- /dev/null +++ b/packaging/glama/Dockerfile @@ -0,0 +1,25 @@ +# Glama MCP directory (glama.ai) check image — NOT required to run the tool. +# +# codebase-memory-mcp ships as a single, fully-static Linux binary (gcc -static) +# and needs NO Docker to run. This image exists only so Glama can build a +# sandbox, launch the stdio MCP server, and run its introspection checks, which +# power the directory's score badge. The same image is exercised locally and in +# CI by packaging/glama/verify.sh to guard against drift. +# +# The binary is static, so the runtime base is arbitrary. We fetch the latest +# released binary for the build platform's architecture (TARGETARCH is amd64 / +# arm64, matching the release asset names). + +FROM debian:bookworm-slim AS fetch +ARG TARGETARCH +RUN apt-get update \ + && apt-get install -y --no-install-recommends curl ca-certificates \ + && curl -fsSL "https://github.com/DeusData/codebase-memory-mcp/releases/latest/download/codebase-memory-mcp-linux-${TARGETARCH}.tar.gz" \ + | tar -xz -C /tmp codebase-memory-mcp \ + && chmod +x /tmp/codebase-memory-mcp \ + && rm -rf /var/lib/apt/lists/* + +FROM debian:bookworm-slim +COPY --from=fetch /tmp/codebase-memory-mcp /usr/local/bin/codebase-memory-mcp +ENV CBM_CACHE_DIR=/tmp/cbm +ENTRYPOINT ["codebase-memory-mcp"] diff --git a/packaging/glama/verify.sh b/packaging/glama/verify.sh new file mode 100755 index 000000000..0a7950a32 --- /dev/null +++ b/packaging/glama/verify.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash +# Build the Glama check image and confirm the MCP stdio server starts and +# answers initialize + tools/list with no project indexed — exactly what +# Glama's directory check does. Used locally and by the CI smoke suite. +# +# Env: +# IMAGE image tag to build (default: cbm-glama-check) +# DOCKER_BUILD_ARGS extra args for `docker build` (e.g. --platform linux/amd64) +set -euo pipefail + +IMAGE="${IMAGE:-cbm-glama-check}" +DIR="$(cd "$(dirname "$0")" && pwd)" + +run_with_timeout() { + local t="$1"; shift + if command -v timeout >/dev/null 2>&1; then timeout "$t" "$@" + elif command -v gtimeout >/dev/null 2>&1; then gtimeout "$t" "$@" + else "$@"; fi +} + +echo "==> building ${IMAGE} (${DIR}/Dockerfile)" +# shellcheck disable=SC2086 +docker build ${DOCKER_BUILD_ARGS:-} -f "${DIR}/Dockerfile" -t "${IMAGE}" "${DIR}" + +echo "==> MCP introspection handshake (initialize -> initialized -> tools/list)" +REQ="$(printf '%s\n%s\n%s\n' \ + '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"glama-verify","version":"1"}}}' \ + '{"jsonrpc":"2.0","method":"notifications/initialized"}' \ + '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}')" +OUT="$(printf '%s' "${REQ}" | run_with_timeout 60 docker run -i --rm "${IMAGE}" || true)" + +printf '%s\n' "${OUT}" + +echo "==> assertions" +printf '%s' "${OUT}" | grep -q '"result"' || { echo "FAIL: no JSON-RPC result (server did not respond)"; exit 1; } +printf '%s' "${OUT}" | grep -q 'search_graph' || { echo "FAIL: tools/list missing expected tool 'search_graph'"; exit 1; } +COUNT="$(printf '%s' "${OUT}" | grep -o '"name"' | wc -l | tr -d ' ')" +echo "PASS: server started and introspected; ~${COUNT} name entries (>=14 tools expected)" From 16103d9e39479d6d3872f261c799c867ee7ce389 Mon Sep 17 00:00:00 2001 From: Martin Vogel Date: Tue, 9 Jun 2026 10:52:44 +0200 Subject: [PATCH 3/4] fix(glama): use portable static binary so the check image runs on any base The standard linux release asset dynamically links glibc 2.38 / libstdc++ (GLIBCXX_3.4.32) and failed to start on debian:bookworm-slim (glibc 2.36). The -portable asset is fully static (gcc -static); the image now builds and passes the MCP initialize + tools/list handshake (15 tools). --- packaging/glama/Dockerfile | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/packaging/glama/Dockerfile b/packaging/glama/Dockerfile index 52d364a72..6b579c447 100644 --- a/packaging/glama/Dockerfile +++ b/packaging/glama/Dockerfile @@ -1,20 +1,20 @@ # Glama MCP directory (glama.ai) check image — NOT required to run the tool. # -# codebase-memory-mcp ships as a single, fully-static Linux binary (gcc -static) -# and needs NO Docker to run. This image exists only so Glama can build a -# sandbox, launch the stdio MCP server, and run its introspection checks, which -# power the directory's score badge. The same image is exercised locally and in -# CI by packaging/glama/verify.sh to guard against drift. +# codebase-memory-mcp needs NO Docker to run. This image exists only so Glama +# can build a sandbox, launch the stdio MCP server, and run its introspection +# checks, which power the directory's score badge. The same image is exercised +# locally and in CI by packaging/glama/verify.sh to guard against drift. # -# The binary is static, so the runtime base is arbitrary. We fetch the latest -# released binary for the build platform's architecture (TARGETARCH is amd64 / -# arm64, matching the release asset names). +# We pull the "-portable" release asset, which is fully statically linked +# (gcc -static) — unlike the standard asset, which dynamically links glibc/ +# libstdc++ and would fail on an older base. Because it's static, the runtime +# base image is arbitrary. TARGETARCH is amd64 / arm64, matching the asset names. FROM debian:bookworm-slim AS fetch ARG TARGETARCH RUN apt-get update \ && apt-get install -y --no-install-recommends curl ca-certificates \ - && curl -fsSL "https://github.com/DeusData/codebase-memory-mcp/releases/latest/download/codebase-memory-mcp-linux-${TARGETARCH}.tar.gz" \ + && curl -fsSL "https://github.com/DeusData/codebase-memory-mcp/releases/latest/download/codebase-memory-mcp-linux-${TARGETARCH}-portable.tar.gz" \ | tar -xz -C /tmp codebase-memory-mcp \ && chmod +x /tmp/codebase-memory-mcp \ && rm -rf /var/lib/apt/lists/* From 69c16cada4e2edee4b713b47d0532883c1f50c15 Mon Sep 17 00:00:00 2001 From: Martin Vogel Date: Tue, 9 Jun 2026 11:28:52 +0200 Subject: [PATCH 4/4] fix: deliver the static -portable linux binary on all install/update paths MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The standard linux release binary dynamically links glibc 2.38+ and GLIBCXX_3.4.32, so it fails to start on Debian 11, Ubuntu 20.04/22.04, RHEL/Rocky 8/9, Amazon Linux 2, etc. — yet install.sh, the npm and PyPI wrappers, and the binary's own self-update all fetched it by default, contradicting the "single static binary" promise. Point every linux install + self-update path at the fully-static "-portable" asset (gcc -static), which has no glibc floor. macOS/Windows are unaffected and unchanged. - install.sh, pkg/npm/install.js, pkg/pypi _cli.py: select -portable on linux - src/cli/cli.c: self-update download URL AND checksum archive name both use -portable on linux (they must match or the update fails checksum verify) - scripts/smoke-test.sh: assert linux self-update targets the -portable asset - scripts/ci/check-glibc-compat.sh: new guard — runs the binary inside debian:bullseye (glibc 2.31) and asserts it starts - _smoke.yml: run the guard on the portable binary in smoke-linux-portable Reproduced: standard binary -> "GLIBC_2.38 not found" on glibc 2.31; portable binary runs cleanly. --- .github/workflows/_smoke.yml | 7 +++++++ install.sh | 10 ++++++++-- pkg/npm/install.js | 6 +++++- pkg/pypi/src/codebase_memory_mcp/_cli.py | 6 +++++- scripts/ci/check-glibc-compat.sh | 22 ++++++++++++++++++++++ scripts/smoke-test.sh | 11 +++++++++++ src/cli/cli.c | 15 +++++++++++---- 7 files changed, 69 insertions(+), 8 deletions(-) create mode 100755 scripts/ci/check-glibc-compat.sh diff --git a/.github/workflows/_smoke.yml b/.github/workflows/_smoke.yml index 75e4506fb..5b8c355bd 100644 --- a/.github/workflows/_smoke.yml +++ b/.github/workflows/_smoke.yml @@ -189,6 +189,13 @@ jobs: chmod +x codebase-memory-mcp scripts/smoke-test.sh ./codebase-memory-mcp + # The -portable binary is what all linux install/update paths now deliver; + # it MUST start on old glibc (Debian 11 / RHEL 8 / Ubuntu 20.04). Runs it + # in debian:bullseye (glibc 2.31) — the standard dynamic binary would fail + # here with `GLIBC_2.38 not found`. + - name: Old-glibc compatibility + run: scripts/ci/check-glibc-compat.sh ./codebase-memory-mcp + - name: Security audits run: | scripts/security-strings.sh ./codebase-memory-mcp diff --git a/install.sh b/install.sh index 3d62249a6..d0678011d 100755 --- a/install.sh +++ b/install.sh @@ -97,10 +97,16 @@ else EXT="tar.gz" fi +# Linux ships a fully-static "-portable" build; the standard linux binary +# dynamically links glibc 2.38+ and fails on older distros (Debian 11, RHEL 8, +# Ubuntu 20.04). macOS/Windows have no such variant. +PORTABLE="" +[ "$OS" = "linux" ] && PORTABLE="-portable" + if [ "$VARIANT" = "ui" ]; then - ARCHIVE="codebase-memory-mcp-ui-${OS}-${ARCH}.${EXT}" + ARCHIVE="codebase-memory-mcp-ui-${OS}-${ARCH}${PORTABLE}.${EXT}" else - ARCHIVE="codebase-memory-mcp-${OS}-${ARCH}.${EXT}" + ARCHIVE="codebase-memory-mcp-${OS}-${ARCH}${PORTABLE}.${EXT}" fi URL="${CBM_DOWNLOAD_URL}/${ARCHIVE}" diff --git a/pkg/npm/install.js b/pkg/npm/install.js index 09873d24d..0b960ef0e 100644 --- a/pkg/npm/install.js +++ b/pkg/npm/install.js @@ -105,7 +105,11 @@ async function main() { fs.mkdirSync(BIN_DIR, { recursive: true }); - const archive = `codebase-memory-mcp-${platform}-${arch}.${ext}`; + // Linux ships a fully-static "-portable" build; the standard linux binary + // dynamically links glibc 2.38+ and fails on older distros. macOS/Windows + // have no such variant. Keep in sync with install.sh / pypi _cli.py / cli.c. + const variant = platform === 'linux' ? '-portable' : ''; + const archive = `codebase-memory-mcp-${platform}-${arch}${variant}.${ext}`; const url = `https://github.com/${REPO}/releases/download/v${VERSION}/${archive}`; process.stdout.write(`codebase-memory-mcp: downloading v${VERSION} for ${platform}/${arch}...\n`); diff --git a/pkg/pypi/src/codebase_memory_mcp/_cli.py b/pkg/pypi/src/codebase_memory_mcp/_cli.py index db4b79283..8013359e3 100644 --- a/pkg/pypi/src/codebase_memory_mcp/_cli.py +++ b/pkg/pypi/src/codebase_memory_mcp/_cli.py @@ -153,7 +153,11 @@ def _download(version: str) -> Path: os_name = _os_name() arch = _arch() ext = "zip" if os_name == "windows" else "tar.gz" - archive = f"codebase-memory-mcp-{os_name}-{arch}.{ext}" + # Linux ships a fully-static "-portable" build; the standard linux binary + # dynamically links glibc 2.38+ and fails on older distros. macOS/Windows + # have no such variant. Keep in sync with install.sh / install.js / cli.c. + variant = "-portable" if os_name == "linux" else "" + archive = f"codebase-memory-mcp-{os_name}-{arch}{variant}.{ext}" url = f"https://github.com/{REPO}/releases/download/v{version}/{archive}" _validate_url_scheme(url) diff --git a/scripts/ci/check-glibc-compat.sh b/scripts/ci/check-glibc-compat.sh new file mode 100755 index 000000000..ed0b8ac50 --- /dev/null +++ b/scripts/ci/check-glibc-compat.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +# Regression guard: the linux binary we ship must START on OLD glibc. +# +# The standard linux release binary dynamically links glibc 2.38+ / GLIBCXX +# 3.4.32 and fails to start on Debian 11, RHEL/Rocky 8, Ubuntu 20.04, Amazon +# Linux 2, etc. The fix points all linux install + self-update paths at the +# fully-static "-portable" asset. This runs a given linux binary inside an +# old-glibc container (debian:bullseye, glibc 2.31) and asserts it starts: +# - RED for the dynamic standard binary (GLIBC_2.38 not found) +# - GREEN for the static -portable binary (runs anywhere) +# +# Usage: check-glibc-compat.sh +# Env: GLIBC_TEST_IMAGE (default: debian:bullseye-slim, glibc 2.31) +set -euo pipefail + +BIN="${1:?usage: check-glibc-compat.sh }" +IMAGE="${GLIBC_TEST_IMAGE:-debian:bullseye-slim}" +BIN_ABS="$(cd "$(dirname "$BIN")" && pwd)/$(basename "$BIN")" + +echo "==> running $(basename "$BIN") --version inside ${IMAGE} (glibc 2.31)" +docker run --rm -v "${BIN_ABS}:/cbm:ro" "${IMAGE}" /cbm --version +echo "PASS: binary starts on old glibc (${IMAGE})" diff --git a/scripts/smoke-test.sh b/scripts/smoke-test.sh index d497d0070..cb8e100a1 100755 --- a/scripts/smoke-test.sh +++ b/scripts/smoke-test.sh @@ -584,6 +584,17 @@ if ! echo "$UPDATE_OUT" | grep -qi 'standard'; then echo "FAIL: update --dry-run did not respect --standard flag" exit 1 fi +# On Linux the binary must self-update from the static "-portable" asset: the +# standard linux asset dynamically links glibc 2.38+ and breaks on older distros +# (Debian 11, RHEL 8, Ubuntu 20.04). Guards build_update_url in src/cli/cli.c. +if [ "$(uname -s)" = "Linux" ]; then + if ! echo "$UPDATE_OUT" | grep -q -- '-portable'; then + echo "FAIL: linux update --dry-run does not target the -portable asset" + echo "$UPDATE_OUT" + exit 1 + fi + echo "OK: linux update targets the -portable (static) asset" +fi echo "OK: update --dry-run --standard completed" # 6d: config set/get/reset round-trip diff --git a/src/cli/cli.c b/src/cli/cli.c index 4d05285c8..4228dbaad 100644 --- a/src/cli/cli.c +++ b/src/cli/cli.c @@ -3761,8 +3761,13 @@ static void build_update_url(char *url, int url_sz, const char *os, const char * if (!base_url || !base_url[0]) { base_url = "https://github.com/DeusData/codebase-memory-mcp/releases/latest/download"; } - snprintf(url, url_sz, "%s/codebase-memory-mcp-%s%s-%s.%s", base_url, want_ui ? "ui-" : "", os, - arch, ext); + /* Linux ships a fully-static "-portable" build; the standard linux binary + * dynamically links glibc 2.38+ and fails on older distros. macOS/Windows + * have no such variant. Keep in sync with install.sh / install.js / pypi + * _cli.py. */ + const char *portable = (strcmp(os, "linux") == 0) ? "-portable" : ""; + snprintf(url, url_sz, "%s/codebase-memory-mcp-%s%s-%s%s.%s", base_url, want_ui ? "ui-" : "", os, + arch, portable, ext); } /* Prompt to delete existing indexes. Returns 0 to continue, 1 to abort. */ @@ -3801,8 +3806,10 @@ static int download_verify_install(const char *url, const char *ext, const char } char archive_name[CLI_BUF_256]; - snprintf(archive_name, sizeof(archive_name), "codebase-memory-mcp-%s%s-%s.%s", - want_ui ? "ui-" : "", os, arch, ext); + /* Must match build_update_url: linux uses the static "-portable" asset. */ + const char *portable = (strcmp(os, "linux") == 0) ? "-portable" : ""; + snprintf(archive_name, sizeof(archive_name), "codebase-memory-mcp-%s%s-%s%s.%s", + want_ui ? "ui-" : "", os, arch, portable, ext); int crc = verify_download_checksum(tmp_archive, archive_name); if (crc == CLI_TRUE) { cbm_unlink(tmp_archive);