d5f207424b
e2e Tests / e2e (macos-latest) (push) Waiting to run
Nix CI / check (push) Waiting to run
Python CI / Python bindings (macos-latest) (push) Waiting to run
e2e Tests / e2e (windows-latest) (push) Waiting to run
Python CI / Python bindings (windows-latest) (push) Waiting to run
Build & Publish / Build Neovim aarch64-apple-darwin (push) Waiting to run
Build & Publish / Build Neovim aarch64-pc-windows-msvc (push) Waiting to run
Build & Publish / Build Neovim x86_64-apple-darwin (push) Waiting to run
Build & Publish / Build Neovim x86_64-pc-windows-msvc (push) Waiting to run
Build & Publish / Build C FFI aarch64-apple-darwin (push) Waiting to run
Build & Publish / Build C FFI aarch64-pc-windows-msvc (push) Waiting to run
Build & Publish / Build C FFI x86_64-apple-darwin (push) Waiting to run
Build & Publish / Build C FFI x86_64-pc-windows-msvc (push) Waiting to run
Build & Publish / Build MCP aarch64-apple-darwin (push) Waiting to run
Build & Publish / Build MCP aarch64-pc-windows-msvc (push) Waiting to run
Build & Publish / Build MCP x86_64-apple-darwin (push) Waiting to run
Build & Publish / Build MCP x86_64-pc-windows-msvc (push) Waiting to run
Build & Publish / Build Python wheels aarch64 (macos-latest) (push) Waiting to run
Build & Publish / Build Python wheels x86_64 (macos-latest) (push) Waiting to run
Build & Publish / Build Python wheels x86_64 (windows-latest) (push) Waiting to run
Build & Publish / Release (push) Blocked by required conditions
Build & Publish / Publish Python wheels to PyPI (push) Blocked by required conditions
Build & Publish / Publish Rust crates (push) Blocked by required conditions
Build & Publish / Publish npm packages (push) Blocked by required conditions
Rust CI / Fuzz Tests (windows-latest) (push) Waiting to run
Rust CI / Test (macos-latest) (push) Waiting to run
Rust CI / Test (windows-latest) (push) Waiting to run
Rust CI / Fuzz Tests (macos-latest) (push) Waiting to run
Python CI / Python bindings (ubuntu-latest) (push) Failing after 0s
Build & Publish / Build Neovim aarch64-linux-android (push) Failing after 0s
Build & Publish / Build Neovim aarch64-unknown-linux-gnu (push) Failing after 0s
Build & Publish / Build MCP x86_64-unknown-linux-musl (push) Failing after 1s
Build & Publish / Build Python wheels aarch64 (ubuntu-latest) (push) Failing after 0s
Build & Publish / Build Python wheels x86_64 (ubuntu-latest) (push) Failing after 1s
Build & Publish / Build Python sdist (push) Failing after 1s
Rust CI / Fuzz Tests (ubuntu-latest) (push) Failing after 1s
Rust CI / Build i686-unknown-linux-gnu (push) Failing after 0s
Rust CI / cargo clippy (push) Failing after 1s
e2e Tests / e2e (ubuntu-latest) (push) Failing after 1s
Lua CI / luacheck lint (push) Failing after 1s
Build & Publish / Build Neovim aarch64-unknown-linux-musl (push) Failing after 1s
Build & Publish / Build Neovim x86_64-unknown-linux-gnu (push) Failing after 0s
Build & Publish / Build Neovim x86_64-unknown-linux-musl (push) Failing after 1s
Build & Publish / Build C FFI aarch64-linux-android (push) Failing after 0s
Build & Publish / Build C FFI aarch64-unknown-linux-gnu (push) Failing after 0s
Build & Publish / Build C FFI aarch64-unknown-linux-musl (push) Failing after 1s
Build & Publish / Build C FFI x86_64-unknown-linux-gnu (push) Failing after 0s
Build & Publish / Build C FFI x86_64-unknown-linux-musl (push) Failing after 1s
Build & Publish / Build MCP aarch64-unknown-linux-gnu (push) Failing after 1s
Build & Publish / Build MCP aarch64-unknown-linux-musl (push) Failing after 0s
Build & Publish / Build MCP x86_64-unknown-linux-gnu (push) Failing after 1s
Spelling / Spell Check with Typos (push) Failing after 0s
Rust CI / Test (ubuntu-latest) (push) Failing after 0s
Rust CI / cargo fmt (push) Failing after 0s
Stylua / Check lua files using Stylua (push) Failing after 1s
Lua CI / lua-language-server type check (push) Failing after 12m29s
e2e Tests / e2e (alpine-musl) (push) Successful in 57m31s
320 lines
9.4 KiB
Bash
Executable File
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.nvim/main/install-mcp.sh | bash
|
|
|
|
REPO="dmtrKovalenko/fff.nvim"
|
|
BINARY_NAME="fff-mcp"
|
|
INSTALL_DIR="${FFF_MCP_INSTALL_DIR:-$HOME/.local/bin}"
|
|
|
|
PINNED_RELEASE_TAG="v0.9.6"
|
|
|
|
SHA256_X86_64_UNKNOWN_LINUX_MUSL="102ceaf173ef776becb3322216e9f6b5caef997c400c5d15f112ce4de40a1f5a"
|
|
SHA256_AARCH64_UNKNOWN_LINUX_MUSL="a9810c9056afa6d9e8ac1e7a3f1f15f8ecbfdc16a592e9d26e4b2434ef97a675"
|
|
SHA256_X86_64_APPLE_DARWIN="58259324c2c13a1b6f24f13138c2cd3eae9ff20e05201a539beb8f2044a651aa"
|
|
SHA256_AARCH64_APPLE_DARWIN="29a7fadeafb062f3e5954b1ab8c69e14dca24f5e061cd8d3b1ea1bab385a3754"
|
|
SHA256_X86_64_PC_WINDOWS_MSVC="7ff688d034aa42ff779a61ad12689794bdc253c895152796046f374390fb9cad"
|
|
SHA256_AARCH64_PC_WINDOWS_MSVC="94ce316a38775d8ed3b32882d8ef45560740319de8d7ac7bb80c31887b3afb6d"
|
|
|
|
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 -- fff-mcp"
|
|
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
|