diff options
Diffstat (limited to 'mcp/run.sh')
| -rwxr-xr-x | mcp/run.sh | 83 |
1 files changed, 83 insertions, 0 deletions
diff --git a/mcp/run.sh b/mcp/run.sh new file mode 100755 index 0000000..27c8ad9 --- /dev/null +++ b/mcp/run.sh @@ -0,0 +1,83 @@ +#!/bin/sh + +# Starts the MCP server (server.mjs), installing its pinned dependencies first when they are +# missing or the lockfile changed. Arguments go to server.mjs: none for stdio, which MCP clients +# launch once per session, or --http HOST:PORT for a shared server. --install-only installs and +# exits, which is worth doing once after cloning so the first client start is not a long one. +# +# stdout is the MCP protocol, so everything here writes to stderr. + +set -eu + +here="$(cd "$(dirname "$0")" && pwd)" + +# MCP clients start servers with a bare environment: Claude Desktop in particular has no node on +# its PATH. Add the usual places one is installed, without disturbing an inherited PATH. +PATH="${PATH}:${HOME}/.local/share/n/bin:${HOME}/.volta/bin:/opt/homebrew/bin:/usr/local/bin" +export PATH + +# nvm installs per version, with no stable bin directory, so take the newest it has. +if ! command -v node >/dev/null 2>&1; then + for dir in "${HOME}"/.nvm/versions/node/*/bin; do + [ -x "${dir}/node" ] && nvm_bin="${dir}" + done + [ -n "${nvm_bin:-}" ] && PATH="${nvm_bin}:${PATH}" && export PATH +fi + +if ! command -v node >/dev/null 2>&1; then + echo "pi-chrome-mcp: no node on PATH; install Node 20.6 or newer, or start this with one on PATH" >&2 + exit 1 +fi + +stamp="${here}/node_modules/.pi-chrome-mcp-deps" +if ! cmp -s "${here}/package-lock.json" "${stamp}" 2>/dev/null; then + # Two clients starting at once would otherwise run npm ci over each other. + lock="${here}/.install-lock" + tries=0 + until mkdir "${lock}" 2>/dev/null; do + tries=$((tries + 1)) + if [ "${tries}" -gt 120 ]; then + echo "pi-chrome-mcp: gave up waiting for ${lock}; remove it if no install is running" >&2 + exit 1 + fi + sleep 1 + done + trap 'rmdir "${lock}" 2>/dev/null || true' EXIT + if ! cmp -s "${here}/package-lock.json" "${stamp}" 2>/dev/null; then + echo "pi-chrome-mcp: installing pinned dependencies" >&2 + npm_config_cache="${here}/.npm-cache" \ + npm ci --prefix "${here}" --no-audit --no-fund --silent >&2 + cp "${here}/package-lock.json" "${stamp}" + fi + rmdir "${lock}" 2>/dev/null || true + trap - EXIT +fi + +# Chrome loads the companion extension from ./extension rather than from node_modules, where it +# ships: npm ci deletes that whole tree when dependencies change, and an unpacked extension whose +# folder disappears is disabled until someone loads it again by hand. This copy is refreshed only +# when pi-chrome's version of it differs, so a reinstall that changes nothing leaves Chrome alone. +# Compared by content, not by the manifest's version: two releases could ship the same version of +# the extension with different files, and a copy that silently stayed behind would be debugged as a +# server bug. +packaged="${here}/node_modules/pi-chrome/extensions/chrome-profile-bridge/browser-extension" +if [ -d "${packaged}" ]; then + digest="$(cd "${packaged}" && find . -type f -print | sort | xargs cat | cksum)" + # Kept beside the folder rather than in it: Chrome loads that folder, and nothing there should + # be anything but the extension. + stamp_ext="${here}/.extension-source" + if [ "${digest}" != "$(cat "${stamp_ext}" 2>/dev/null || true)" ]; then + echo "pi-chrome-mcp: updating ./extension; reload it at chrome://extensions" >&2 + rm -rf "${here}/extension.new" + cp -R "${packaged}" "${here}/extension.new" + rm -rf "${here}/extension" + mv "${here}/extension.new" "${here}/extension" + echo "${digest}" > "${stamp_ext}" + fi +fi + +if [ "${1:-}" = "--install-only" ]; then + echo "pi-chrome-mcp: ready. Load this folder in chrome://extensions: ${here}/extension" >&2 + exit 0 +fi +exec node "${here}/server.mjs" "$@" |
