Files
dmtrkovalenko--fff/install-mcp.sh
T
Joe Taber cc289f0f93 chore: rename old fff.nvim repo refs (#762)
* chore: point residual repo refs at dmtrKovalenko/fff

Update install URLs, download targets, and docs after the GitHub
rename from fff.nvim to fff. Preserve the existing Neovim package
name (fff.nvim) in lazy/vim.pack snippets so upgraders keep the same
install dir and lockfile identity.

* chore(docs): align Neovim package name with repo basename

Drop the explicit fff.nvim package name so lazy/vim.pack use fff from
the repository URL. Mild migration cost for existing installs (new
plugin dir + clean of the old one). Drop this commit to keep the
lowest-pain name=fff.nvim install snippets from the previous commit.
2026-08-11 21:40:21 -07:00

320 lines
9.4 KiB
Bash
Executable File

#!/usr/bin/env bash
set -eo pipefail
# FFF MCP Server installer
# Usage: curl -fsSL https://raw.githubusercontent.com/dmtrKovalenko/fff/main/install-mcp.sh | bash
REPO="dmtrKovalenko/fff"
BINARY_NAME="fff-mcp"
INSTALL_DIR="${FFF_MCP_INSTALL_DIR:-$HOME/.local/bin}"
PINNED_RELEASE_TAG="v0.10.3"
SHA256_X86_64_UNKNOWN_LINUX_MUSL="8823476699977f3dbfe351862f083163810f66656522007cbb65be140c71994c"
SHA256_AARCH64_UNKNOWN_LINUX_MUSL="f2092d02c26523476b19361c3fbe2e82033232efdd2e0bee9e7cdbbb31e9436a"
SHA256_X86_64_APPLE_DARWIN="f72c31c7f3355d37ec526ba56fd716341bd7186a15a399910cf3f2b46fef2099"
SHA256_AARCH64_APPLE_DARWIN="8c52acf1e95540f02cf55579126ebf580cf48af15c76e1c90301c2c8c5acbcf9"
SHA256_X86_64_PC_WINDOWS_MSVC="7e4d5c007e65e70f7070b3fa39867cd48827873acebf981d933eef7b339759ce"
SHA256_AARCH64_PC_WINDOWS_MSVC="ef677422bf5344b3e4c5952c6b5885f3e9b4d0e7608684de1f5fd56bcd3d22ad"
expected_sha_for() {
case "$1" in
x86_64-unknown-linux-musl) echo "$SHA256_X86_64_UNKNOWN_LINUX_MUSL" ;;
aarch64-unknown-linux-musl) echo "$SHA256_AARCH64_UNKNOWN_LINUX_MUSL" ;;
x86_64-apple-darwin) echo "$SHA256_X86_64_APPLE_DARWIN" ;;
aarch64-apple-darwin) echo "$SHA256_AARCH64_APPLE_DARWIN" ;;
x86_64-pc-windows-msvc) echo "$SHA256_X86_64_PC_WINDOWS_MSVC" ;;
aarch64-pc-windows-msvc) echo "$SHA256_AARCH64_PC_WINDOWS_MSVC" ;;
esac
}
info() { printf '\033[1;34m%s\033[0m\n' "$*"; }
success() { printf '\033[1;38;5;208m%s\033[0m\n' "$*"; }
warn() { printf '\033[1;33m%s\033[0m\n' "$*"; }
error() { printf '\033[1;31mError: %s\033[0m\n' "$*" >&2; exit 1; }
# Print JSON with syntax highlighting via jq if available, plain otherwise
print_json() {
if command -v jq &>/dev/null; then
echo "$1" | jq .
else
echo "$1"
fi
}
detect_platform() {
local os arch target
os="$(uname -s)"
arch="$(uname -m)"
case "$os" in
Linux)
# Prefer musl (static) for maximum compatibility
case "$arch" in
x86_64) target="x86_64-unknown-linux-musl" ;;
aarch64|arm64) target="aarch64-unknown-linux-musl" ;;
*) error "Unsupported architecture: $arch" ;;
esac
;;
Darwin)
case "$arch" in
x86_64) target="x86_64-apple-darwin" ;;
aarch64|arm64) target="aarch64-apple-darwin" ;;
*) error "Unsupported architecture: $arch" ;;
esac
;;
MINGW*|MSYS*|CYGWIN*)
case "$arch" in
x86_64) target="x86_64-pc-windows-msvc" ;;
aarch64|arm64) target="aarch64-pc-windows-msvc" ;;
*) error "Unsupported architecture: $arch" ;;
esac
;;
*) error "Unsupported OS: $os" ;;
esac
echo "$target"
}
get_latest_release_tag() {
local target="$1"
# Honor the pin baked in by `make bump-install-mcp-sh`. Required when SHAs
# are pinned, since fetching /releases would race against newer releases.
if [ -n "$PINNED_RELEASE_TAG" ]; then
echo "$PINNED_RELEASE_TAG"
return
fi
local releases_json
local curl_args=(-fsSL)
# Use gh CLI token if available to avoid rate limiting
if command -v gh &>/dev/null; then
local gh_token
gh_token="$(gh auth token 2>/dev/null || true)"
if [ -n "$gh_token" ]; then
curl_args+=(-H "Authorization: token $gh_token")
fi
fi
releases_json=$(curl "${curl_args[@]}" "https://api.github.com/repos/${REPO}/releases") \
|| error "Failed to fetch releases from https://github.com/${REPO}/releases"
# Find the first release that contains an fff-mcp binary for our platform
local tag
tag=$(echo "$releases_json" \
| grep -oE '"(tag_name|name)": *"[^"]*"' \
| awk -v target="fff-mcp-${target}" '
/"tag_name":/ { gsub(/.*": *"|"/, ""); current_tag = $0; next }
/"name":/ && index($0, target) { print current_tag; exit }
')
if [ -z "$tag" ]; then
error "No release found containing fff-mcp binaries for ${target}. The MCP build may not have been released yet."
fi
echo "$tag"
}
download_binary() {
local target="$1"
local tag="$2"
local ext=""
case "$target" in
*windows*) ext=".exe" ;;
esac
local filename="${BINARY_NAME}-${target}${ext}"
local url="https://github.com/${REPO}/releases/download/${tag}/${filename}"
local checksum_url="${url}.sha256"
info "Downloading ${filename} from release ${tag}..."
local tmp_dir
tmp_dir="$(mktemp -d)"
trap 'rm -rf "$tmp_dir"' EXIT
if ! curl -fsSL -o "${tmp_dir}/${filename}" "$url" 2>/dev/null; then
echo "" >&2
printf '\033[1;31mError: Failed to download binary for your platform.\033[0m\n' >&2
echo "" >&2
echo " URL: ${url}" >&2
echo " Release: ${tag}" >&2
echo " Platform: ${target}" >&2
echo "" >&2
echo "This likely means the MCP binary hasn't been built for this release yet." >&2
echo "Check available releases at: https://github.com/${REPO}/releases" >&2
exit 1
fi
# Verify checksum: prefer the SHA pinned in this script (offline, tamper-evident).
# Fall back to the .sha256 file on the release for targets/releases without a pin.
if command -v sha256sum &>/dev/null; then
local pinned_sha
pinned_sha="$(expected_sha_for "$target")"
if [ -n "$pinned_sha" ]; then
info "Verifying checksum against pinned value..."
echo "${pinned_sha} ${filename}" > "${tmp_dir}/${filename}.sha256"
(cd "$tmp_dir" && sha256sum -c "${filename}.sha256") \
|| error "Checksum verification failed!"
elif curl -fsSL -o "${tmp_dir}/${filename}.sha256" "$checksum_url" 2>/dev/null; then
info "Verifying checksum..."
(cd "$tmp_dir" && sha256sum -c "${filename}.sha256") \
|| error "Checksum verification failed!"
else
warn "Checksum file not available, skipping verification."
fi
fi
# Install
mkdir -p "$INSTALL_DIR"
mv "${tmp_dir}/${filename}" "${INSTALL_DIR}/${BINARY_NAME}${ext}"
chmod +x "${INSTALL_DIR}/${BINARY_NAME}${ext}"
if [ "$IS_UPDATE" != true ]; then
success "Installed ${BINARY_NAME} to ${INSTALL_DIR}/${BINARY_NAME}${ext}"
fi
}
check_path() {
case ":$PATH:" in
*":${INSTALL_DIR}:"*) return 0 ;;
esac
warn "${INSTALL_DIR} is not in your PATH."
echo ""
echo "Add it to your shell profile:"
echo ""
local shell_name
shell_name="$(basename "${SHELL:-bash}")"
case "$shell_name" in
zsh)
echo " echo 'export PATH=\"${INSTALL_DIR}:\$PATH\"' >> ~/.zshrc"
echo " source ~/.zshrc"
;;
fish)
echo " fish_add_path ${INSTALL_DIR}"
;;
*)
echo " echo 'export PATH=\"${INSTALL_DIR}:\$PATH\"' >> ~/.bashrc"
echo " source ~/.bashrc"
;;
esac
echo ""
}
print_setup_instructions() {
local binary_path="${INSTALL_DIR}/${BINARY_NAME}"
local found_any=false
echo ""
success "FFF MCP Server installed successfully!"
echo ""
info "Setup with your AI coding assistant:"
echo ""
# Claude Code
if command -v claude &>/dev/null; then
found_any=true
success "[Claude Code] detected"
echo ""
echo "Global (recommended):"
echo "claude mcp add -s user fff -- ${binary_path}"
echo ""
echo "Or project-level .mcp.json (uses PATH):"
echo ""
print_json '{
"mcpServers": {
"fff": {
"type": "stdio",
"command": "fff-mcp",
"args": []
}
}
}'
echo ""
fi
# OpenCode
if command -v opencode &>/dev/null; then
found_any=true
success "[OpenCode] detected"
echo ""
echo "Add to ~/.config/opencode/opencode.json:"
echo ""
print_json '{
"mcp": {
"fff": {
"type": "local",
"command": ["fff-mcp"],
"enabled": true
}
}
}'
echo ""
fi
# Codex
if command -v codex &>/dev/null; then
found_any=true
success "[Codex] detected"
echo ""
echo "codex mcp add fff -- \"${binary_path}\""
echo ""
fi
if [ "$found_any" = false ]; then
echo "No AI coding assistants detected."
echo ""
echo "Binary path: ${binary_path}"
echo ""
fi
echo "Binary: ${binary_path}"
echo "Docs: https://github.com/${REPO}"
echo ""
info "Tip: Add this to your CLAUDE.md or AGENTS.md to make AI use fff for all searches:"
echo "\""
echo "Use the fff MCP tools for all file search operations instead of default tools."
echo "\""
}
main() {
local target
target="$(detect_platform)"
local existing_binary="${INSTALL_DIR}/${BINARY_NAME}"
IS_UPDATE=false
if [ -x "$existing_binary" ]; then
IS_UPDATE=true
info "Updating FFF MCP Server..."
else
info "Installing FFF MCP Server..."
fi
echo ""
info "Detected platform: ${target}"
local tag
tag="$(get_latest_release_tag "$target")"
download_binary "$target" "$tag"
if [ "$IS_UPDATE" = true ]; then
echo ""
success "FFF MCP Server updated to ${tag}!"
echo ""
else
check_path
print_setup_instructions
fi
}
main