#!/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."