Newer
Older
navi-1 / deploy / sync-mcp-venvs.sh
#!/usr/bin/env bash
# Sync the private venv of every in-repo MCP server.
#
# Each server under mcp-servers/ runs from its own venv — its config in
# mcp_servers.d/*.json points `command` at <server>/.venv/bin/python — and
# Navi's .venv does not carry those dependencies. A dependency added to one of
# those pyproject.toml files therefore needs this install; rebuilding Navi's
# venv does not cover it. When the step was missed (trafilatura, Nov 2026) the
# server died at import, the manager could not connect, and every one of its
# tools silently disappeared from the tool list — no error reached the chat.
#
# Idempotent and cheap: the sha256 of pyproject.toml is stamped inside the venv
# and an unchanged project is skipped, so this is safe to run after every pull.
# `deploy/install.sh` calls it; run it directly on a host where install.sh is
# not part of the update (prod) or after editing one server's pyproject.toml.

set -euo pipefail

REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$REPO_DIR"

say() { printf '\n\033[1;34m==> %s\033[0m\n' "$*"; }
err() { printf '\033[1;31mERROR: %s\033[0m\n' "$*" >&2; }

# Only needed for a venv that does not exist yet — an existing one keeps its
# own interpreter. Fall back to the standalone build install.sh may have
# fetched into .python/.
PYBIN=""
for cand in python3.13 python3.12 python3.11 python3; do
    command -v "$cand" >/dev/null 2>&1 || continue
    if "$cand" -c 'import sys; sys.exit(0 if sys.version_info >= (3, 11) else 1)'; then
        PYBIN="$cand"
        break
    fi
done
if [ -z "$PYBIN" ]; then
    # .python/ is install.sh's standalone fallback; a bare `python3.11` from
    # the loop above is a PATH name, so only the absolute fallback is `-x`-tested.
    PYBIN="$REPO_DIR/.python/python/bin/python3"
    if [ ! -x "$PYBIN" ]; then
        err "no python >= 3.11 and no .python/ build — run deploy/install.sh first"
        exit 1
    fi
fi

say "Syncing MCP servers' own venvs"

shopt -s nullglob
synced=0
for proj in mcp-servers/*/pyproject.toml; do
    dir="$(dirname "$proj")"
    name="$(basename "$dir")"
    # _template is scaffolding for a new server, not a server.
    [ "$name" != "_template" ] || continue
    synced=1

    want="$(sha256sum "$proj" | cut -d' ' -f1)"
    stamp="$dir/.venv/.navi-pyproject.sha256"
    if [ -f "$stamp" ] && [ "$(cat "$stamp")" = "$want" ]; then
        echo "OK: $name (pyproject unchanged)"
        continue
    fi

    if [ ! -x "$dir/.venv/bin/python" ]; then
        "$PYBIN" -m venv "$dir/.venv"
    fi
    "$dir/.venv/bin/pip" install -q --upgrade pip
    "$dir/.venv/bin/pip" install -q -e "$dir"
    printf '%s\n' "$want" > "$stamp"
    echo "Installed: $name"
done

if [ "$synced" = 0 ]; then
    echo "No MCP servers with a pyproject.toml — nothing to sync."
fi

echo "MCP server venvs are in sync."