Skip to main content
Glama

Docker Manager MCP

install.sh•17 kB
#!/bin/bash # Docker Manager MCP - Automated Installer # This script sets up Docker Manager MCP with automatic SSH key configuration # Usage: curl -sSL https://raw.githubusercontent.com/jmagar/docker-mcp/main/install.sh | bash set -e # Color codes for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color # Configuration DOCKER_MCP_DIR="${HOME}/.docker-mcp" SSH_KEY_NAME="docker-mcp-key" SSH_KEY_PATH="${DOCKER_MCP_DIR}/ssh/${SSH_KEY_NAME}" CONTAINER_SSH_KEY_PATH="/home/dockermcp/.ssh/${SSH_KEY_NAME}" CONFIG_DIR="${DOCKER_MCP_DIR}/config" DATA_DIR="${DOCKER_MCP_DIR}/data" COMPOSE_URL="https://raw.githubusercontent.com/jmagar/docker-mcp/main/docker-compose.yaml" EXAMPLE_CONFIG_URL="https://raw.githubusercontent.com/jmagar/docker-mcp/main/config/hosts.example.yml" AUTO_APPROVE="${AUTO_APPROVE:-true}" # Functions print_header() { echo -e "${BLUE}========================================${NC}" echo -e "${BLUE}Docker Manager MCP Installer${NC}" echo -e "${BLUE}========================================${NC}" echo } print_success() { echo -e "${GREEN}āœ“${NC} $1" } print_error() { echo -e "${RED}āœ—${NC} $1" } print_warning() { echo -e "${YELLOW}⚠${NC} $1" } print_info() { echo -e "${BLUE}ℹ${NC} $1" } auto_confirm() { case "${AUTO_APPROVE,,}" in y|yes|true|1) return 0 ;; *) return 1 ;; esac } should_skip_host() { local alias="$1" local hostname="$2" [[ -z "$alias" ]] && return 0 [[ "$alias" == "*" ]] && return 0 [[ "$alias" == "?" ]] && return 0 [[ "$alias" == "localhost" ]] && return 0 [[ "$alias" == "127.0.0.1" ]] && return 0 [[ "$hostname" == "localhost" ]] && return 0 [[ "$hostname" == "127.0.0.1" ]] && return 0 case "$hostname" in github.com|gitlab.com|bitbucket.org) return 0 ;; esac case "$alias" in github.com|gitlab.com|bitbucket.org) return 0 ;; esac return 1 } collect_host_entries() { local ssh_config="${HOME}/.ssh/config" if [ ! -f "$ssh_config" ]; then return 1 fi declare -A seen_alias local entries=() while IFS= read -r line || [ -n "$line" ]; do line="${line%%#*}" line="${line%$' '}" [[ -z "$line" ]] && continue if [[ $line =~ ^[Hh]ost[[:space:]]+(.+)$ ]]; then local hosts_line="${BASH_REMATCH[1]}" for alias in $hosts_line; do [[ -z "$alias" ]] && continue if [[ "$alias" == "host" ]]; then continue fi if [[ "$alias" == "host" ]] || [[ "$alias" == "none" ]]; then continue fi if [[ "$alias" == !* ]] || [[ "$alias" == *"*"* ]] || [[ "$alias" == *"?"* ]]; then continue fi if [[ -n "${seen_alias[$alias]}" ]]; then continue fi seen_alias[$alias]=1 local config_output if ! config_output=$(ssh -G "$alias" 2>/dev/null); then continue fi local hostname user port hostname=$(echo "$config_output" | awk '/^hostname / {print $2; exit}') user=$(echo "$config_output" | awk '/^user / {print $2; exit}') port=$(echo "$config_output" | awk '/^port / {print $2; exit}') user=${user:-$USER} port=${port:-22} if ! should_skip_host "$alias" "$hostname"; then entries+=("$alias|$hostname|$user|$port") fi done fi done < "$ssh_config" if [ ${#entries[@]} -eq 0 ]; then return 1 fi printf '%s ' "${entries[@]}" } ensure_known_hosts() { local entries mapfile -t entries < <(collect_host_entries) || entries=() if [ ${#entries[@]} -eq 0 ]; then return fi local known_hosts_file="${DOCKER_MCP_DIR}/ssh/known_hosts" touch "$known_hosts_file" chmod 600 "$known_hosts_file" if ! command -v ssh-keyscan >/dev/null 2>&1; then print_warning "ssh-keyscan not available; host authenticity prompts may appear on first connect" return fi local tmp tmp=$(mktemp) for entry in "${entries[@]}"; do IFS='|' read -r alias hostname user port <<< "$entry" hostname=${hostname:-$alias} port=${port:-22} if output=$(ssh-keyscan -T 10 -p "$port" -H "$hostname" 2>/dev/null); then echo "$output" >> "$tmp" else print_warning "Could not prefetch host key for $hostname" fi done cat "$tmp" >> "$known_hosts_file" rm -f "$tmp" sort -u "$known_hosts_file" -o "$known_hosts_file" } check_command() { if command -v "$1" &> /dev/null; then print_success "$1 is installed" return 0 else print_error "$1 is not installed" return 1 fi } check_prerequisites() { echo -e "${BLUE}Checking prerequisites...${NC}" echo local missing_deps=0 if ! check_command docker; then missing_deps=1 echo " Please install Docker: https://docs.docker.com/get-docker/" fi if ! check_command docker-compose && ! docker compose version &> /dev/null; then missing_deps=1 echo " Please install Docker Compose: https://docs.docker.com/compose/install/" fi if ! check_command ssh; then missing_deps=1 echo " Please install OpenSSH client" fi if ! check_command ssh-keygen; then missing_deps=1 echo " Please install OpenSSH client (ssh-keygen)" fi if ! check_command ssh-copy-id; then print_warning "ssh-copy-id not found - you'll need to manually copy SSH keys" fi echo if [ $missing_deps -eq 1 ]; then print_error "Missing required dependencies. Please install them and run this script again." exit 1 fi print_success "All prerequisites met!" echo } create_directories() { echo -e "${BLUE}Creating directory structure...${NC}" echo mkdir -p "${DOCKER_MCP_DIR}" mkdir -p "${DOCKER_MCP_DIR}/ssh" mkdir -p "${CONFIG_DIR}" mkdir -p "${DATA_DIR}/logs" # Create symlink to SSH config if it exists (for host resolution) if [ -f "${HOME}/.ssh/config" ]; then ln -sf "${HOME}/.ssh/config" "${DOCKER_MCP_DIR}/ssh/config" 2>/dev/null || true print_info "Linked SSH config for host resolution" fi print_success "Created directory structure at ${DOCKER_MCP_DIR}" echo } generate_ssh_keys() { echo -e "${BLUE}Generating SSH keys for Docker MCP...${NC}" echo if [ -f "${SSH_KEY_PATH}" ]; then print_warning "SSH key already exists at ${SSH_KEY_PATH}" if auto_confirm; then print_info "Using existing SSH key" return fi read -p "Do you want to regenerate it? (y/N): " -n 1 -r echo if [[ ! $REPLY =~ ^[Yy]$ ]]; then print_info "Using existing SSH key" return fi fi ssh-keygen -t ed25519 -f "${SSH_KEY_PATH}" -N "" -C "docker-mcp@$(hostname)" chmod 600 "${SSH_KEY_PATH}" chmod 644 "${SSH_KEY_PATH}.pub" print_success "Generated SSH key pair at ${SSH_KEY_PATH}" echo } copy_ssh_keys() { echo -e "${BLUE}Distributing SSH keys to hosts...${NC}" echo if [ ! -f "${SSH_KEY_PATH}.pub" ]; then print_error "SSH public key not found at ${SSH_KEY_PATH}.pub" return 1 fi local entries mapfile -t entries < <(collect_host_entries) || entries=() if [ ${#entries[@]} -eq 0 ]; then print_warning "No suitable hosts found in SSH config for key distribution" print_info "Public key is available at ${SSH_KEY_PATH}.pub if you need to copy it manually" echo return fi print_info "Preparing to copy SSH key to ${#entries[@]} host(s)" for entry in "${entries[@]}"; do IFS='|' read -r alias hostname user port <<< "$entry" echo " - ${alias} (${user}@${hostname}:${port})" done echo local REPLY if auto_confirm; then REPLY="y" else read -p "Proceed with key distribution? (Y/n): " -n 1 -r echo fi if [[ ! ${REPLY,,} =~ ^(y|)$ ]]; then print_warning "Skipping SSH key distribution" print_info "You can manually copy the key later using: ssh-copy-id -i ${SSH_KEY_PATH} user@host" echo return fi local failed_hosts=() for entry in "${entries[@]}"; do IFS='|' read -r alias hostname user port <<< "$entry" echo -n "Copying key to ${alias}... " if command -v ssh-copy-id >/dev/null 2>&1; then if ssh-copy-id -i "${SSH_KEY_PATH}" "${alias}" >/dev/null 2>&1; then print_success "Success" else print_error "Failed" failed_hosts+=("${alias}") fi else if cat "${SSH_KEY_PATH}.pub" | ssh "${alias}" "mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys" >/dev/null 2>&1; then print_success "Success" else print_error "Failed" failed_hosts+=("${alias}") fi fi done echo if [ ${#failed_hosts[@]} -gt 0 ]; then print_warning "Failed to copy keys to the following hosts:" for host in "${failed_hosts[@]}"; do echo " - ${host}" done echo print_info "You can manually copy the key using: ssh-copy-id -i ${SSH_KEY_PATH} user@host" echo else print_success "Successfully distributed SSH keys to all hosts" echo fi } generate_hosts_config() { echo -e "${BLUE}Generating hosts configuration...${NC}" echo local config_file="${CONFIG_DIR}/hosts.yml" local entries mapfile -t entries < <(collect_host_entries) || entries=() cat > "$config_file" << 'EOF' # Docker Manager MCP Configuration # Auto-generated from SSH config hosts: EOF if [ ${#entries[@]} -eq 0 ]; then print_warning "No hosts were imported from SSH config" print_info "Downloading example configuration..." curl -sSL "$EXAMPLE_CONFIG_URL" -o "$config_file" print_info "Please edit ${config_file} to add your Docker hosts" echo return fi for entry in "${entries[@]}"; do IFS='|' read -r alias hostname user port <<< "$entry" cat >> "$config_file" << EOF ${alias}: hostname: ${hostname} user: ${user} port: ${port} identity_file: ${CONTAINER_SSH_KEY_PATH} description: "Imported from SSH config" tags: ["imported", "ssh-config"] enabled: true EOF done print_success "Generated hosts configuration at ${config_file}" echo } find_available_port() { local start_port="${1:-8000}" local max_port=$((start_port + 100)) for port in $(seq $start_port $max_port); do # Check if port is in use if ! lsof -Pi :$port -sTCP:LISTEN -t >/dev/null 2>&1 && \ ! netstat -tuln 2>/dev/null | grep -q ":$port " && \ ! ss -tuln 2>/dev/null | grep -q ":$port "; then echo "$port" return 0 fi done # If no port found in range, return error return 1 } download_compose_file() { echo -e "${BLUE}Downloading docker-compose.yaml...${NC}" echo local compose_file="${DOCKER_MCP_DIR}/docker-compose.yaml" if curl -sSL "$COMPOSE_URL" -o "$compose_file"; then # Update paths in compose file sed -i.bak "s|~/.ssh|${DOCKER_MCP_DIR}/ssh|g" "$compose_file" sed -i.bak "s|./config|${CONFIG_DIR}|g" "$compose_file" sed -i.bak "s|./data|${DATA_DIR}|g" "$compose_file" rm -f "${compose_file}.bak" # Check if port 8000 is available local desired_port=8000 if lsof -Pi :$desired_port -sTCP:LISTEN -t >/dev/null 2>&1 || \ netstat -tuln 2>/dev/null | grep -q ":$desired_port " || \ ss -tuln 2>/dev/null | grep -q ":$desired_port "; then print_warning "Port $desired_port is already in use" print_info "Finding an available port..." if available_port=$(find_available_port $desired_port); then print_success "Found available port: $available_port" desired_port=$available_port else print_error "Could not find an available port in range 8000-8100" print_info "Please manually edit the port in ${compose_file}" desired_port=8000 # Use default for display purposes fi else print_success "Port $desired_port is available" fi cat > "${DOCKER_MCP_DIR}/.env" << EOF FASTMCP_PORT=${desired_port} SSH_KEYS_HOST_PATH=${DOCKER_MCP_DIR}/ssh FASTMCP_DATA_DIR=${DATA_DIR} DOCKER_MCP_DATA_DIR=${DATA_DIR} LOG_DIR=${DATA_DIR}/logs EOF # Export for use in other functions export FASTMCP_PORT=$desired_port print_success "Downloaded docker-compose.yaml to ${compose_file}" else print_error "Failed to download docker-compose.yaml" exit 1 fi echo } start_services() { echo -e "${BLUE}Starting Docker MCP services...${NC}" echo cd "${DOCKER_MCP_DIR}" # Check if docker-compose or docker compose should be used if command -v docker-compose &> /dev/null; then COMPOSE_CMD="docker-compose" else COMPOSE_CMD="docker compose" fi print_info "Pulling Docker image..." $COMPOSE_CMD pull print_info "Starting services..." if $COMPOSE_CMD up -d; then print_success "Docker MCP services started successfully!" else print_error "Failed to start services" print_info "Check logs with: cd ${DOCKER_MCP_DIR} && $COMPOSE_CMD logs" exit 1 fi echo } print_completion() { echo -e "${GREEN}========================================${NC}" echo -e "${GREEN}Installation Complete!${NC}" echo -e "${GREEN}========================================${NC}" echo echo "Docker MCP has been installed and configured at:" echo " ${DOCKER_MCP_DIR}" echo echo "Configuration files:" echo " Hosts config: ${CONFIG_DIR}/hosts.yml" echo " SSH key: ${SSH_KEY_PATH}" echo " Environment: ${DOCKER_MCP_DIR}/.env" echo " Docker Compose: ${DOCKER_MCP_DIR}/docker-compose.yaml" echo echo "Persistent data directory:" echo " ${DATA_DIR}" echo echo "Service is running at:" echo " http://localhost:${FASTMCP_PORT:-8000}" echo echo "Useful commands:" echo " View logs: cd ${DOCKER_MCP_DIR} && docker compose logs -f" echo " Stop: cd ${DOCKER_MCP_DIR} && docker compose down" echo " Restart: cd ${DOCKER_MCP_DIR} && docker compose restart" echo " Update: cd ${DOCKER_MCP_DIR} && docker compose pull && docker compose up -d" echo echo "To use with Claude Desktop, add to config:" echo ' {' echo ' "mcpServers": {' echo ' "docker-mcp": {' echo " \"url\": \"http://localhost:${FASTMCP_PORT:-8000}\"" echo ' }' echo ' }' echo ' }' echo } setup_ssh_with_standalone_script() { echo -e "${BLUE}Setting up SSH keys...${NC}" echo # Fix SC2155: declare then assign to avoid command substitution in local local script_dir local script_path # Robust script directory computation supporting sourcing script_dir="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)" script_path="$script_dir/scripts/setup-ssh-keys.sh" if [ -f "$script_path" ]; then print_info "Using standalone SSH setup script" # Use bash to invoke script (no dependency on executable bit) if DOCKER_MCP_SSH_KEY_NAME="$SSH_KEY_NAME" \ DOCKER_MCP_CONTAINER_SSH_KEY_PATH="$CONTAINER_SSH_KEY_PATH" \ DOCKER_MCP_DIR="$DOCKER_MCP_DIR" \ bash "$script_path" --batch; then print_success "SSH key distribution completed successfully" else print_warning "SSH setup script encountered issues, falling back to embedded functions" generate_ssh_keys copy_ssh_keys generate_hosts_config fi else print_info "Standalone script not found, using embedded SSH setup" generate_ssh_keys ensure_known_hosts copy_ssh_keys generate_hosts_config fi echo } main() { print_header check_prerequisites create_directories setup_ssh_with_standalone_script ensure_known_hosts download_compose_file start_services print_completion } # Run main function main "$@"

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/jmagar/docker-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server