#!/bin/bash
# FedMCP Installer for Claude Desktop (macOS & Linux)
# Usage: curl -fsSL https://raw.githubusercontent.com/northernvariables/CanadaGPT/main/packages/fedmcp/scripts/install-claude-desktop.sh | bash
#
# For Windows, use: irm https://raw.githubusercontent.com/northernvariables/CanadaGPT/main/packages/fedmcp/scripts/install-claude-desktop.ps1 | iex
set -euo pipefail
# ============================================================================
# Configuration
# ============================================================================
FEDMCP_REPO="https://github.com/northernvariables/CanadaGPT.git"
FEDMCP_INSTALL_DIR="$HOME/.fedmcp"
FEDMCP_VENV="$FEDMCP_INSTALL_DIR/venv"
# ============================================================================
# Colors (with fallback for non-color terminals)
# ============================================================================
if [[ -t 1 ]] && [[ "${TERM:-}" != "dumb" ]]; then
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
BOLD='\033[1m'
NC='\033[0m'
else
RED=''
GREEN=''
YELLOW=''
BLUE=''
BOLD=''
NC=''
fi
# ============================================================================
# Helper Functions
# ============================================================================
info() { echo -e "${BLUE}ℹ${NC} $1"; }
success() { echo -e "${GREEN}✓${NC} $1"; }
warn() { echo -e "${YELLOW}⚠${NC} $1"; }
error() { echo -e "${RED}✗${NC} $1" >&2; }
fatal() { error "$1"; exit 1; }
cleanup() {
local exit_code=$?
if [[ $exit_code -ne 0 ]]; then
echo ""
error "Installation failed. For help, visit:"
error "https://github.com/northernvariables/CanadaGPT/issues"
fi
}
trap cleanup EXIT
# ============================================================================
# Banner
# ============================================================================
echo -e "${BLUE}"
cat << 'EOF'
╔═══════════════════════════════════════════════════════════════╗
║ 🍁 FedMCP Desktop Installer ║
║ Canadian Federal Government Data for Claude Desktop ║
╚═══════════════════════════════════════════════════════════════╝
EOF
echo -e "${NC}"
# ============================================================================
# Platform Detection
# ============================================================================
detect_platform() {
local os
os="$(uname -s)"
case "$os" in
Darwin*)
PLATFORM="macos"
CONFIG_DIR="$HOME/Library/Application Support/Claude"
;;
Linux*)
# Check for WSL
if grep -qi microsoft /proc/version 2>/dev/null; then
PLATFORM="wsl"
warn "WSL detected. This installer sets up fedmcp for Linux."
warn "If using Claude Desktop on Windows, paths won't work."
echo ""
info "For Windows Claude Desktop, run in PowerShell instead:"
info " irm .../install-claude-desktop.ps1 | iex"
echo ""
# In curl|bash, /dev/tty lets us read from terminal
if [[ -t 0 ]]; then
read -r -p "Continue with Linux installation? [y/N] " response
else
read -r -p "Continue with Linux installation? [y/N] " response < /dev/tty 2>/dev/null || response="y"
fi
if [[ ! "$response" =~ ^[Yy]$ ]]; then
info "Installation cancelled."
exit 0
fi
CONFIG_DIR="$HOME/.config/Claude"
else
PLATFORM="linux"
CONFIG_DIR="$HOME/.config/Claude"
fi
;;
MINGW*|MSYS*|CYGWIN*)
echo ""
fatal "Windows detected. Please use the PowerShell installer:
irm https://raw.githubusercontent.com/northernvariables/CanadaGPT/main/packages/fedmcp/scripts/install-claude-desktop.ps1 | iex"
;;
*)
fatal "Unsupported operating system: $os"
;;
esac
CONFIG_FILE="$CONFIG_DIR/claude_desktop_config.json"
success "Platform: $PLATFORM"
}
# ============================================================================
# Dependency Checks
# ============================================================================
check_dependencies() {
info "Checking dependencies..."
# Check Python
if ! command -v python3 &>/dev/null; then
if [[ "$PLATFORM" == "macos" ]]; then
fatal "python3 not found. Install with: brew install python@3.12"
else
fatal "python3 not found. Install with: sudo apt install python3"
fi
fi
local py_version
py_version=$(python3 -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")')
if ! python3 -c "import sys; exit(0 if sys.version_info >= (3, 10) else 1)" 2>/dev/null; then
fatal "Python 3.10+ required (found $py_version). Please upgrade Python."
fi
success "Python $py_version"
# Check venv module
if ! python3 -c "import venv" 2>/dev/null; then
if [[ "$PLATFORM" == "linux" ]] || [[ "$PLATFORM" == "wsl" ]]; then
fatal "Python venv module not found. Install with:
sudo apt install python3-venv # Debian/Ubuntu
sudo dnf install python3-venv # Fedora
sudo pacman -S python # Arch"
else
fatal "Python venv module not found."
fi
fi
success "Python venv module"
# Check git
if ! command -v git &>/dev/null; then
if [[ "$PLATFORM" == "macos" ]]; then
fatal "git not found. Install with: xcode-select --install"
else
fatal "git not found. Install with: sudo apt install git"
fi
fi
success "git $(git --version | cut -d' ' -f3)"
echo ""
}
# ============================================================================
# Installation
# ============================================================================
install_fedmcp() {
info "Installing FedMCP..."
# Create install directory
mkdir -p "$FEDMCP_INSTALL_DIR"
# Check if pipx is available (preferred method)
if command -v pipx &>/dev/null; then
info "Using pipx (recommended)..."
# Uninstall first if exists, then install fresh (most reliable)
pipx uninstall fedmcp 2>/dev/null || true
pipx install "git+${FEDMCP_REPO}#subdirectory=packages/fedmcp"
# Get the pipx venv python path
local pipx_venvs
pipx_venvs=$(pipx environment --value PIPX_LOCAL_VENVS 2>/dev/null || echo "$HOME/.local/pipx/venvs")
PYTHON_PATH="$pipx_venvs/fedmcp/bin/python"
if [[ ! -f "$PYTHON_PATH" ]]; then
warn "Could not locate pipx venv, falling back to venv method"
install_with_venv
fi
else
install_with_venv
fi
success "FedMCP installed"
echo ""
}
install_with_venv() {
info "Creating virtual environment at $FEDMCP_VENV..."
# Remove existing venv for clean install (avoids stale state)
if [[ -d "$FEDMCP_VENV" ]]; then
info "Removing existing installation for clean upgrade..."
rm -rf "$FEDMCP_VENV"
fi
python3 -m venv "$FEDMCP_VENV"
# Upgrade pip (suppress output)
"$FEDMCP_VENV/bin/pip" install --upgrade pip --quiet 2>/dev/null || true
# Install fedmcp
info "Installing fedmcp package (this may take a minute)..."
"$FEDMCP_VENV/bin/pip" install "git+${FEDMCP_REPO}#subdirectory=packages/fedmcp"
PYTHON_PATH="$FEDMCP_VENV/bin/python"
}
# ============================================================================
# Verification
# ============================================================================
verify_installation() {
info "Verifying installation..."
# Check Python path exists
if [[ ! -f "$PYTHON_PATH" ]]; then
fatal "Python executable not found at: $PYTHON_PATH"
fi
# Check fedmcp is importable
if ! "$PYTHON_PATH" -c "import fedmcp" 2>/dev/null; then
fatal "Verification failed: fedmcp module not importable.
Try reinstalling:
rm -rf ~/.fedmcp
# Then run this installer again"
fi
# Get version if available
local installed_version
installed_version=$("$PYTHON_PATH" -c "import fedmcp; print(getattr(fedmcp, '__version__', 'installed'))" 2>/dev/null || echo "installed")
success "FedMCP $installed_version verified"
echo ""
}
# ============================================================================
# Claude Desktop Configuration
# ============================================================================
configure_claude_desktop() {
info "Configuring Claude Desktop..."
# Create config directory
mkdir -p "$CONFIG_DIR"
# Backup existing config if present
if [[ -f "$CONFIG_FILE" ]]; then
local backup_file="$CONFIG_FILE.backup.$(date +%Y%m%d%H%M%S)"
cp "$CONFIG_FILE" "$backup_file"
info "Backed up existing config to: $(basename "$backup_file")"
fi
# Use Python to safely merge configs (handles malformed JSON gracefully)
"$PYTHON_PATH" << PYTHON_SCRIPT
import json
import sys
import os
config_file = """$CONFIG_FILE"""
python_path = """$PYTHON_PATH"""
# New fedmcp config
fedmcp_config = {
"command": python_path,
"args": ["-m", "fedmcp.server"]
}
# Load existing config or create new
config = {}
if os.path.exists(config_file):
try:
with open(config_file, 'r') as f:
content = f.read().strip()
if content:
config = json.loads(content)
except json.JSONDecodeError as e:
print(f"⚠ Existing config was malformed, creating fresh config", file=sys.stderr)
config = {}
except Exception as e:
print(f"⚠ Could not read existing config: {e}", file=sys.stderr)
config = {}
# Ensure mcpServers exists
if not isinstance(config.get('mcpServers'), dict):
config['mcpServers'] = {}
# Add/update fedmcp
config['mcpServers']['fedmcp'] = fedmcp_config
# Write atomically (write to temp, then rename)
temp_file = config_file + '.tmp'
try:
with open(temp_file, 'w') as f:
json.dump(config, f, indent=2)
f.write('\n') # Trailing newline
# Validate what we wrote
with open(temp_file, 'r') as f:
json.load(f) # Will raise if invalid
# Atomic rename
os.replace(temp_file, config_file)
except Exception as e:
if os.path.exists(temp_file):
os.remove(temp_file)
print(f"Error writing config: {e}", file=sys.stderr)
sys.exit(1)
PYTHON_SCRIPT
success "Claude Desktop configured"
info "Config: $CONFIG_FILE"
echo ""
}
# ============================================================================
# Success Message
# ============================================================================
print_success() {
echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e "${GREEN}${BOLD} 🎉 Installation Complete! 🎉${NC}"
echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo ""
echo -e "${YELLOW}${BOLD}▶ Next step: Quit and reopen Claude Desktop${NC}"
echo ""
echo "FedMCP gives Claude access to 75+ tools for Canadian government data:"
echo " • Parliamentary debates (Hansard)"
echo " • MP voting records and expenses"
echo " • Bill tracking and legislative analysis"
echo " • Lobbying registrations"
echo " • Federal contracts and spending"
echo ""
echo -e "${BLUE}Example prompts to try:${NC}"
echo ' "What bills are currently being debated in Parliament?"'
echo ' "Show me Pierre Poilievre'\''s voting record"'
echo ' "Who is lobbying on artificial intelligence?"'
echo ""
echo -e "${YELLOW}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e "${YELLOW}Optional: Add API keys for higher rate limits${NC}"
echo -e "${YELLOW}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo ""
echo "Edit $CONFIG_FILE and add an \"env\" block:"
echo ""
echo ' "fedmcp": {'
echo ' "command": "...",'
echo ' "args": ["-m", "fedmcp.server"],'
echo ' "env": {'
echo ' "FEDMCP_API_KEY": "your-canadagpt-pro-key"'
echo ' }'
echo ' }'
echo ""
echo "Get keys at:"
echo " • CanadaGPT PRO (10k req/hr): https://canadagpt.ca/settings"
echo " • CanLII (legal search): https://canlii.org/en/feedback/feedback.html"
echo ""
}
# ============================================================================
# Main
# ============================================================================
main() {
detect_platform
check_dependencies
install_fedmcp
verify_installation
configure_claude_desktop
print_success
}
main "$@"