build_and_run.sh•49.9 kB
#!/bin/bash
# Build and run MCP Server for Splunk
# This script builds the Docker image and runs it with docker-compose, or falls back to local mode
#
# Usage:
# ./build_and_run.sh # Interactive mode - choose deployment method
# ./build_and_run.sh --docker # Force Docker deployment
# ./build_and_run.sh --local # Force local deployment
#
# Arguments:
# --docker Force Docker deployment (skip choice dialog)
# --local Force local deployment (skip choice dialog)
# --help Show this help message
set -e # Exit on error
# Parse command line arguments first (before any other logic)
DEPLOYMENT_MODE=""
FORCE_MODE=false
STOP_MODE=false
while [[ $# -gt 0 ]]; do
case $1 in
--docker)
DEPLOYMENT_MODE="docker"
FORCE_MODE=true
shift
;;
--local)
DEPLOYMENT_MODE="local"
FORCE_MODE=true
shift
;;
--stop)
STOP_MODE=true
shift
;;
--help|-h)
echo "Usage: $0 [OPTIONS]"
echo
echo "Options:"
echo " --docker Force Docker deployment (skip choice dialog)"
echo " --local Force local deployment (skip choice dialog)"
echo " --stop Stop all Docker services and clean up"
echo " --help, -h Show this help message"
echo
echo "Examples:"
echo " $0 # Interactive mode - choose deployment method"
echo " $0 --docker # Force Docker deployment"
echo " $0 --local # Force local deployment"
echo " $0 --stop # Stop all Docker services"
echo
exit 0
;;
*)
echo "Error: Unknown option: $1" >&2
echo "Use --help for usage information" >&2
exit 1
;;
esac
done
# Change to the project root directory (parent of scripts)
cd "$(dirname "$0")/.."
echo "🚀 Building and Running MCP Server for Splunk"
echo "============================================="
echo
echo "📚 Need help with prerequisites? See: docs/getting-started/installation.md"
echo
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
PURPLE='\033[0;35m'
NC='\033[0m' # No Color
# Function to print colored output
print_status() {
echo -e "${BLUE}[INFO]${NC} $1"
}
print_success() {
echo -e "${GREEN}[SUCCESS]${NC} $1"
}
print_warning() {
echo -e "${YELLOW}[WARNING]${NC} $1"
}
print_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
print_local() {
echo -e "${PURPLE}[LOCAL]${NC} $1"
}
# Function to ensure logs directory exists
ensure_logs_dir() {
if [ ! -d "logs" ]; then
print_local "Creating logs directory..."
mkdir -p logs
fi
}
# Function to prompt for Splunk configuration and update .env file
prompt_splunk_config() {
local is_docker_mode=${1:-false}
echo
print_status "🔧 Splunk Configuration Setup"
echo "=================================="
echo
# Check if .env file exists, create from example if not
if [ ! -f .env ]; then
if [ -f env.example ]; then
print_local "Creating .env file from env.example..."
cp env.example .env
print_success ".env file created from env.example"
else
print_error "env.example not found. Cannot create .env file."
return 1
fi
fi
# Read current values from .env file
local current_host=$(grep "^SPLUNK_HOST=" .env 2>/dev/null | cut -d'=' -f2 | sed -e 's/^"//' -e 's/"$//' -e "s/^'//" -e "s/'$//" || echo "")
local current_port=$(grep "^SPLUNK_PORT=" .env 2>/dev/null | cut -d'=' -f2 | sed -e 's/^"//' -e 's/"$//' -e "s/^'//" -e "s/'$//" || echo "8089")
local current_username=$(grep "^SPLUNK_USERNAME=" .env 2>/dev/null | cut -d'=' -f2 | sed -e 's/^"//' -e 's/"$//' -e "s/^'//" -e "s/'$//" || echo "admin")
local current_password=$(grep "^SPLUNK_PASSWORD=" .env 2>/dev/null | cut -d'=' -f2 | sed -e 's/^"//' -e 's/"$//' -e "s/^'//" -e "s/'$//" || echo "")
echo "Current Splunk configuration:"
echo " Host: ${current_host:-'Not set'}"
echo " Port: ${current_port:-'8089'}"
echo " Username: ${current_username:-'admin'}"
echo " Password: ${current_password:+'***'}"
echo
# If in Docker mode and SPLUNK_HOST is not 'so1', offer to restore Docker defaults
local docker_defaults_restored=false
if [ "$is_docker_mode" = true ] && [ "$current_host" != "so1" ] && [ -n "$current_host" ]; then
echo "🚢 Docker Mode Detected:"
echo " Current SPLUNK_HOST ($current_host) is different from Docker default (so1)"
echo " This will use external Splunk instead of the included Docker container"
echo
read -p "Restore Docker defaults (so1) to include Splunk container? (y/N): " restore_docker
if [[ "$restore_docker" =~ ^[Yy]$ ]]; then
current_host="so1"
current_port="8089"
current_username="admin"
current_password="Chang3d!"
docker_defaults_restored=true
print_success "Restored Docker defaults: SPLUNK_HOST=so1, SPLUNK_PORT=8089, SPLUNK_USERNAME=admin, SPLUNK_PASSWORD=Chang3d!"
echo
fi
fi
# Prompt for new values (skip if Docker defaults were restored)
if [ "$docker_defaults_restored" = true ]; then
# Use restored Docker defaults directly
local final_host="$current_host"
local final_port="$current_port"
local final_username="$current_username"
local final_password="$current_password"
print_local "Using restored Docker defaults, skipping user input prompts..."
else
# Prompt for new values
read -p "Enter Splunk host/URL (current: ${current_host:-'Not set'}, press Enter to keep): " new_host
read -p "Enter Splunk port (current: ${current_port:-'8089'}, press Enter to keep): " new_port
read -p "Enter Splunk username (current: ${current_username:-'admin'}, press Enter to keep): " new_username
read -s -p "Enter Splunk password (press Enter to keep current): " new_password
echo
# Use new values if provided, otherwise keep current
local final_host=${new_host:-$current_host}
local final_port=${new_port:-$current_port}
local final_username=${new_username:-$current_username}
local final_password=${new_password:-$current_password}
fi
# Parse URL if provided (strip protocol and extract hostname)
if [ -n "$new_host" ]; then
if [[ "$new_host" =~ ^https?://(.+)$ ]]; then
final_host="${BASH_REMATCH[1]}"
print_local "Extracted hostname from URL: $final_host"
print_local "Note: SSL verification setting unchanged (preserves private CA configuration)"
fi
fi
# Validate required fields
if [ -z "$final_host" ]; then
print_error "SPLUNK_HOST is required. Please provide a value."
return 1
fi
if [ -z "$final_username" ]; then
print_error "SPLUNK_USERNAME is required. Please provide a value."
return 1
fi
if [ -z "$final_password" ]; then
print_error "SPLUNK_PASSWORD is required. Please provide a value."
return 1
fi
# Check if there are actual changes to make
local has_changes=false
if [ "$docker_defaults_restored" = true ]; then
has_changes=true
elif [ "$new_host" != "" ] || [ "$new_port" != "" ] || [ "$new_username" != "" ] || [ "$new_password" != "" ]; then
has_changes=true
fi
# Show final configuration
echo
echo "Final Splunk configuration:"
echo " Host: $final_host"
echo " Port: $final_port"
echo " Username: $final_username"
echo " Password: ***"
echo
# Only ask for confirmation if there are actual changes
if [ "$has_changes" = true ]; then
# If Docker defaults were restored, automatically update without asking
if [ "$docker_defaults_restored" = true ]; then
print_status "Automatically updating .env file with restored Docker defaults..."
# Update .env file
local temp_env=".env.tmp"
# Process .env file line by line
while IFS= read -r line || [ -n "$line" ]; do
if [[ "$line" =~ ^SPLUNK_HOST= ]]; then
echo "SPLUNK_HOST='$final_host'" >> "$temp_env"
elif [[ "$line" =~ ^SPLUNK_PORT= ]]; then
echo "SPLUNK_PORT='$final_port'" >> "$temp_env"
elif [[ "$line" =~ ^SPLUNK_USERNAME= ]]; then
echo "SPLUNK_USERNAME='$final_username'" >> "$temp_env"
elif [[ "$line" =~ ^SPLUNK_PASSWORD= ]]; then
echo "SPLUNK_PASSWORD='$final_password'" >> "$temp_env"
else
echo "$line" >> "$temp_env"
fi
done < .env
# Replace original .env with updated version
mv "$temp_env" .env
print_success ".env file updated successfully with Docker defaults!"
else
# Ask for confirmation for other types of changes
read -p "Update .env file with these settings? (y/N): " confirm
if [[ "$confirm" =~ ^[Yy]$ ]]; then
# Update .env file
local temp_env=".env.tmp"
# Process .env file line by line
while IFS= read -r line || [ -n "$line" ]; do
if [[ "$line" =~ ^SPLUNK_HOST= ]]; then
echo "SPLUNK_HOST='$final_host'" >> "$temp_env"
elif [[ "$line" =~ ^SPLUNK_PORT= ]]; then
echo "SPLUNK_PORT='$final_port'" >> "$temp_env"
elif [[ "$line" =~ ^SPLUNK_USERNAME= ]]; then
echo "SPLUNK_USERNAME='$final_username'" >> "$temp_env"
elif [[ "$line" =~ ^SPLUNK_PASSWORD= ]]; then
echo "SPLUNK_PASSWORD='$final_password'" >> "$temp_env"
else
echo "$line" >> "$temp_env"
fi
done < .env
# Replace original .env with updated version
mv "$temp_env" .env
print_success ".env file updated successfully!"
else
print_warning "Configuration update cancelled. Using existing values."
fi
fi
else
print_local "No changes detected. Using existing configuration."
fi
# Always export the values for current session (whether updated or existing)
export SPLUNK_HOST="$final_host"
export SPLUNK_PORT="$final_port"
export SPLUNK_USERNAME="$final_username"
export SPLUNK_PASSWORD="$final_password"
print_success "Splunk configuration loaded for current session."
return 0
}
# Function to load environment variables from .env file
load_env_file() {
if [ -f .env ]; then
print_local "Loading environment variables from .env file..."
# Export variables from .env file, handling comments and empty lines
while IFS= read -r line || [ -n "$line" ]; do
# Skip empty lines and comments
if [[ "$line" =~ ^[[:space:]]*$ ]] || [[ "$line" =~ ^[[:space:]]*# ]]; then
continue
fi
# Check if line contains an assignment
if [[ "$line" =~ ^[[:space:]]*([A-Za-z_][A-Za-z0-9_]*)=(.*)$ ]]; then
local var_name="${BASH_REMATCH[1]}"
local var_value="${BASH_REMATCH[2]}"
# Remove surrounding quotes if present
var_value=$(echo "$var_value" | sed -e 's/^"//' -e 's/"$//' -e "s/^'//" -e "s/'$//")
# Export the variable
export "$var_name=$var_value"
# Only show non-sensitive variables
if [[ "$var_name" == *"PASSWORD"* ]] || [[ "$var_name" == *"SECRET"* ]] || [[ "$var_name" == *"TOKEN"* ]]; then
print_local "Loaded: $var_name=***"
else
print_local "Loaded: $var_name=$var_value"
fi
fi
done < .env
print_success "Environment variables loaded from .env file!"
# Show summary of important Splunk configuration
echo
print_status "📋 Splunk Configuration Summary:"
echo " 🌐 Host: ${SPLUNK_HOST:-'Not set'}"
echo " 🔌 Port: ${SPLUNK_PORT:-'8089 (default)'}"
echo " 👤 User: ${SPLUNK_USERNAME:-'Not set'}"
# Show last 3 chars of password for verification
if [ -n "$SPLUNK_PASSWORD" ]; then
local pass_display="***${SPLUNK_PASSWORD: -3}"
echo " 🔐 Pass: $pass_display"
else
echo " 🔐 Pass: Not set"
fi
echo " 🔒 SSL: ${SPLUNK_VERIFY_SSL:-'Not set'}"
# Check for alternative MCP_SPLUNK_* variables
if [ -n "$MCP_SPLUNK_HOST" ] || [ -n "$MCP_SPLUNK_USERNAME" ] || [ -n "$MCP_SPLUNK_PASSWORD" ]; then
echo
print_status "📋 Alternative MCP Splunk Configuration Found:"
echo " 🌐 Host: ${MCP_SPLUNK_HOST:-'Not set'}"
echo " 👤 User: ${MCP_SPLUNK_USERNAME:-'Not set'}"
# Show last 3 chars of MCP password for verification
if [ -n "$MCP_SPLUNK_PASSWORD" ]; then
local mcp_pass_display="***${MCP_SPLUNK_PASSWORD: -3}"
echo " 🔐 Pass: $mcp_pass_display"
else
echo " 🔐 Pass: Not set"
fi
print_local "These MCP_SPLUNK_* variables will override the SPLUNK_* variables in the MCP server."
fi
echo
else
print_warning "No .env file found. Using system environment variables only."
# Check if any Splunk environment variables are set in the system
if [ -n "$SPLUNK_HOST" ] || [ -n "$MCP_SPLUNK_HOST" ]; then
print_status "📋 System Environment Splunk Configuration:"
echo " 🌐 Host: ${SPLUNK_HOST:-${MCP_SPLUNK_HOST:-'Not set'}}"
echo " 👤 User: ${SPLUNK_USERNAME:-${MCP_SPLUNK_USERNAME:-'Not set'}}"
# Show last 3 chars of password for verification
local sys_password="${SPLUNK_PASSWORD:-$MCP_SPLUNK_PASSWORD}"
if [ -n "$sys_password" ]; then
local sys_pass_display="***${sys_password: -3}"
echo " 🔐 Pass: $sys_pass_display"
else
echo " 🔐 Pass: Not set"
fi
else
print_warning "No Splunk configuration found in environment variables."
print_warning "The MCP server may not be able to connect to Splunk without configuration."
fi
fi
}
# Function to check if uv is installed
check_uv() {
if command -v uv &> /dev/null; then
return 0
else
return 1
fi
}
# Function to install uv
install_uv() {
print_status "Installing uv package manager..."
if command -v curl &> /dev/null; then
curl -LsSf https://astral.sh/uv/install.sh | sh
# Source the shell to get uv in PATH
export PATH="$HOME/.cargo/bin:$PATH"
elif command -v wget &> /dev/null; then
wget -qO- https://astral.sh/uv/install.sh | sh
export PATH="$HOME/.cargo/bin:$PATH"
else
print_error "Neither curl nor wget found. Please install uv manually:"
print_error " pip install uv"
echo
print_error "📚 For detailed installation instructions, see:"
print_error " docs/getting-started/installation.md#-uv-package-manager-installation"
exit 1
fi
# Verify installation
if check_uv; then
print_success "uv installed successfully!"
else
print_error "Failed to install uv. Please install manually and try again."
echo
print_error "📚 For detailed installation instructions, see:"
print_error " docs/getting-started/installation.md#-uv-package-manager-installation"
exit 1
fi
}
# Function to setup local environment
setup_local_env() {
print_local "Setting up local development environment..."
# Check if uv is available
if ! check_uv; then
print_warning "uv not found. Installing uv..."
install_uv
fi
# Check if virtual environment and dependencies are installed
if [ ! -d ".venv" ] || [ ! -f ".venv/pyvenv.cfg" ]; then
print_local "Creating virtual environment and installing dependencies..."
uv sync --dev
else
print_local "Virtual environment exists. Checking if sync is needed..."
# Check if uv.lock is newer than .venv
if [ "uv.lock" -nt ".venv/pyvenv.cfg" ] || [ "pyproject.toml" -nt ".venv/pyvenv.cfg" ]; then
print_local "Dependencies may be outdated. Running uv sync..."
uv sync --dev
else
print_local "Dependencies are up to date."
fi
fi
# Check if .env file exists
if [ ! -f .env ]; then
print_warning ".env file not found. Creating from env.example..."
cp env.example .env
print_warning "Created .env file. You may want to edit it with your Splunk configuration."
print_warning "For local development, you can also use MCP_SPLUNK_* environment variables."
fi
# Prompt for Splunk configuration with local mode
prompt_splunk_config false
# Load environment variables from .env file
load_env_file
print_success "Local environment setup complete!"
}
# Function to run local server
run_local_server() {
print_local "Starting MCP server locally with FastMCP CLI..."
# Define cleanup function early to be available for error handling
cleanup() {
print_local "Cleaning up..."
# Kill MCP Inspector if PID file exists
if [ -f .inspector_pid ]; then
local pid=$(cat .inspector_pid)
if kill -0 $pid 2>/dev/null; then
print_local "Stopping MCP Inspector (PID: $pid)..."
kill $pid 2>/dev/null
fi
rm -f .inspector_pid
fi
# Clean up any server processes
pkill -f "fastmcp run" 2>/dev/null || true
# Clean up log files
rm -f logs/inspector.log logs/mcp_server.log
print_success "Cleanup complete!"
}
# Check if Node.js/npm is available for MCP Inspector (start after server port is known)
local inspector_available=false
local inspector_supported=false
if command -v node &> /dev/null && command -v npx &> /dev/null; then
# Check Node.js version (MCP Inspector 0.16.x requires Node.js 22+)
local node_version=$(node --version 2>/dev/null | sed 's/v//')
local node_major=$(echo "$node_version" | cut -d. -f1)
if [ "$node_major" -ge 22 ] 2>/dev/null; then
inspector_supported=true
print_local "Node.js v$node_version detected. MCP Inspector will be started after the MCP server is running..."
else
print_warning "Node.js v$node_version detected, but MCP Inspector 0.16.x requires Node.js 22+"
print_warning "MCP Inspector will not be available."
print_warning "To upgrade Node.js: https://nodejs.org/"
echo
print_warning "📚 For detailed installation instructions, see:"
print_warning " docs/getting-started/installation.md#-nodejs-installation-optional---for-mcp-inspector"
fi
else
print_warning "Node.js/npx not found. MCP Inspector will not be available."
print_warning "To install Node.js: https://nodejs.org/"
echo
print_warning "📚 For detailed installation instructions, see:"
print_warning " docs/getting-started/installation.md#-nodejs-installation-optional---for-mcp-inspector"
fi
echo
print_status "Starting MCP server..."
# Test if the FastMCP command works first
print_local "Testing FastMCP installation..."
if ! uv run python -c "import fastmcp; print('FastMCP import successful')"; then
print_error "FastMCP import failed. Checking installation..."
print_local "Installing FastMCP..."
uv add fastmcp
uv sync --dev
fi
# Start the server with uv and better error handling
print_local "Finding available port for MCP server..."
local preferred_port=${MCP_SERVER_PORT:-8003}
print_local "Preferred port from MCP_SERVER_PORT: $preferred_port"
# Start from preferred port to avoid conflict with Splunk Web UI (port 8000)
local mcp_port=$(find_available_port $preferred_port)
if [ "$mcp_port" -ne $preferred_port ] 2>/dev/null; then
print_warning "Port $preferred_port is in use. Using port $mcp_port instead."
else
print_local "Using port $mcp_port for MCP server."
fi
print_local "Starting MCP server on port $mcp_port..."
print_local "Command: uv run fastmcp run src/server.py --transport http --port $mcp_port"
# Ensure logs directory exists
ensure_logs_dir
# Start server in background to check if it starts successfully
uv run fastmcp run src/server.py --transport http --port $mcp_port > logs/mcp_server.log 2>&1 &
local server_pid=$!
# Give server time to start
print_local "Waiting for MCP server to start..."
sleep 3
# Check if the server process is still running
if ! kill -0 $server_pid 2>/dev/null; then
print_error "MCP server failed to start. Check the logs:"
echo
if [ -f logs/mcp_server.log ]; then
print_error "=== MCP Server Log ==="
cat logs/mcp_server.log
echo
fi
print_error "Troubleshooting steps:"
echo "1. Check if src/server.py exists and is valid"
echo "2. Verify FastMCP installation: uv run python -c 'import fastmcp'"
echo "3. Try running manually: uv run fastmcp run src/server.py --help"
echo "4. Check Python environment: uv run python --version"
echo
print_error "📚 For prerequisite installation help, see:"
print_error " docs/getting-started/installation.md"
echo
print_error "🔧 Run the prerequisite checker to see what's missing:"
print_error " ./scripts/check-prerequisites.sh"
cleanup
exit 1
fi
# Check if the chosen port is actually listening
print_local "Checking if MCP server is listening on port $mcp_port..."
local port_check_attempts=0
local max_port_attempts=5
local server_listening=false
while [ $port_check_attempts -lt $max_port_attempts ]; do
if command -v lsof &> /dev/null; then
if lsof -i :$mcp_port &> /dev/null; then
server_listening=true
break
fi
elif command -v netstat &> /dev/null; then
if netstat -an | grep ":$mcp_port " | grep LISTEN &> /dev/null; then
server_listening=true
break
fi
elif command -v curl &> /dev/null; then
# Try to actually connect to the server
if curl -s -o /dev/null -w "%{http_code}" "http://localhost:$mcp_port" | grep -q "200\|404\|500"; then
server_listening=true
break
fi
fi
sleep 2
port_check_attempts=$((port_check_attempts + 1))
print_local "Port check attempt $port_check_attempts/$max_port_attempts..."
done
if [ "$server_listening" = true ]; then
print_success "MCP server is listening on port $mcp_port!"
# Also test basic connectivity
if command -v curl &> /dev/null; then
print_local "Testing server connectivity..."
local response=$(curl -s -o /dev/null -w "%{http_code}" "http://localhost:$mcp_port" 2>/dev/null || echo "failed")
if [ "$response" != "failed" ]; then
print_success "Server responds with HTTP $response"
else
print_warning "Server running but HTTP request failed"
fi
fi
# Start MCP Inspector now that the actual port is known
if [ "$inspector_supported" = true ]; then
print_status "Starting MCP Inspector..."
# If inspector is already running, reuse it
if (command -v lsof &> /dev/null && lsof -i :6274 &> /dev/null) || (command -v netstat &> /dev/null && netstat -an | grep ":6274 " | grep LISTEN &> /dev/null); then
print_warning "MCP Inspector appears to already be running on port 6274"
print_success "Using existing MCP Inspector instance"
inspector_available=true
else
# Configure environment for inspector (env vars, not CLI flags)
export DANGEROUSLY_OMIT_AUTH=true
export MCP_AUTO_OPEN_ENABLED=false
# Use command-line arguments for more reliable configuration
local inspector_url="http://localhost:$mcp_port/mcp/"
print_local "Configuring MCP Inspector to connect to: $inspector_url"
ensure_logs_dir
# Install and start MCP Inspector 0.16.5 specifically
print_local "Installing MCP Inspector 0.16.5..."
npx --yes @modelcontextprotocol/inspector@0.16.5 --transport streamable-http --server-url "$inspector_url" > logs/inspector.log 2>&1 &
local inspector_pid=$!
print_local "Waiting for MCP Inspector to start..."
local attempts=0
local max_attempts=10
local inspector_running=false
local last_log_size=0
while [ $attempts -lt $max_attempts ]; do
sleep 2
if ! kill -0 $inspector_pid 2>/dev/null; then
print_error "MCP Inspector process died. Check inspector.log for details."
break
fi
if [ -f logs/inspector.log ]; then
local current_log_size=$(wc -c < logs/inspector.log 2>/dev/null || echo 0)
if [ $current_log_size -gt $last_log_size ]; then
local last_line=$(tail -1 logs/inspector.log | grep -v "^$" || true)
if [ -n "$last_line" ]; then
print_local "Inspector: $last_line"
fi
last_log_size=$current_log_size
fi
fi
# Enhanced health check: Wait for HTTP endpoint to be accessible
if command -v curl &> /dev/null; then
print_local "Testing MCP Inspector HTTP endpoint (http://localhost:6274)..."
if curl -s -f "http://localhost:6274" > /dev/null 2>&1; then
inspector_running=true
print_success "MCP Inspector HTTP endpoint is responding!"
break
else
print_local "HTTP endpoint not yet responding (attempt $attempts/$max_attempts)..."
fi
elif command -v lsof &> /dev/null; then
if lsof -i :6274 &> /dev/null; then
inspector_running=true
break
fi
elif command -v netstat &> /dev/null; then
if netstat -an | grep ":6274 " | grep LISTEN &> /dev/null; then
inspector_running=true
break
fi
else
if [ -f logs/inspector.log ] && grep -q "MCP Inspector is up and running" logs/inspector.log; then
inspector_running=true
break
fi
if [ $attempts -ge 5 ]; then
inspector_running=true
break
fi
fi
attempts=$((attempts + 1))
print_local "Attempt $attempts/$max_attempts - waiting for inspector..."
done
if [ "$inspector_running" = true ]; then
# Final verification: Ensure the HTTP endpoint is fully accessible
if command -v curl &> /dev/null; then
print_local "Final verification: Testing MCP Inspector accessibility..."
local final_attempts=0
local max_final_attempts=5
local final_success=false
while [ $final_attempts -lt $max_final_attempts ]; do
if curl -s -f "http://localhost:6274" > /dev/null 2>&1; then
final_success=true
break
fi
sleep 1
final_attempts=$((final_attempts + 1))
print_local "Final verification attempt $final_attempts/$max_final_attempts..."
done
if [ "$final_success" = true ]; then
print_success "MCP Inspector started successfully on port 6274 and is fully accessible!"
echo $inspector_pid > .inspector_pid
inspector_available=true
else
print_warning "MCP Inspector started but HTTP endpoint is not fully accessible"
inspector_available=false
fi
else
print_success "MCP Inspector started successfully on port 6274"
echo $inspector_pid > .inspector_pid
inspector_available=true
fi
else
print_warning "Failed to start MCP Inspector"
if [ -f logs/inspector.log ]; then
print_warning "Inspector log (last 10 lines):"
tail -10 logs/inspector.log
echo
fi
if kill -0 $inspector_pid 2>/dev/null; then
kill $inspector_pid 2>/dev/null
fi
inspector_available=false
fi
fi
fi
else
print_error "MCP server is not listening on port $mcp_port"
print_error "Server process ID: $server_pid"
# Show server logs for debugging
if [ -f logs/mcp_server.log ]; then
print_error "=== MCP Server Log (last 20 lines) ==="
tail -20 logs/mcp_server.log
echo
fi
print_error "Attempting to restart server in foreground for debugging..."
kill $server_pid 2>/dev/null
sleep 2
print_local "Running server in foreground mode for debugging..."
print_local "If this works, there might be a background process issue..."
# Try running in foreground
exec uv run fastmcp run src/server.py --transport http --port $mcp_port
fi
# Update cleanup function to include server PID
cleanup() {
print_local "Shutting down services..."
# Kill MCP Server if it was started by this script
if [ -n "$server_pid" ] && kill -0 $server_pid 2>/dev/null; then
print_local "Stopping MCP Server (PID: $server_pid)..."
kill $server_pid 2>/dev/null
fi
# Kill MCP Inspector if it was started by this script
if [ -f .inspector_pid ]; then
local pid=$(cat .inspector_pid)
if kill -0 $pid 2>/dev/null; then
print_local "Stopping MCP Inspector (PID: $pid)..."
kill $pid 2>/dev/null
fi
rm -f .inspector_pid
fi
# Clean up log files
rm -f logs/inspector.log logs/inspector_alt.log logs/mcp_server.log
print_success "Cleanup complete!"
exit 0
}
# Re-set up signal handling for cleanup with updated function
trap cleanup INT TERM
# Keep the script running and monitoring
print_success "✅ MCP Server setup complete!"
# Show final summary with access points
echo
print_status "🎉 Local MCP Server Ready!"
echo
print_status "📋 Access Points:"
echo " 🔌 MCP Server (stdio): Available for MCP clients"
echo " 🔌 MCP Server (HTTP): http://localhost:$mcp_port"
if [ "$inspector_available" = true ]; then
echo " 📊 MCP Inspector: http://localhost:6274"
echo
print_status "🎯 Testing Instructions:"
echo " 1. Open http://localhost:6274 in your browser"
echo " 2. Ensure that Streamable HTTP is set"
echo " 3. MCP Inspector is pre-configured to connect to port $mcp_port"
echo " 4. Click 'Connect' button at the bottom"
echo " 5. Test tools and resources interactively"
else
echo
print_status "🎯 Alternative Testing:"
echo " 1. Open https://inspector.mcp.dev/ in your browser"
echo " 2. Ensure that Streamable HTTP is set"
echo " 3. Enter server URL: http://localhost:$mcp_port/mcp/"
echo " 4. Click 'Connect' button at the bottom"
echo " 5. Test tools and resources interactively"
echo
print_warning "💡 MCP Inspector Troubleshooting:"
echo " • Check if Node.js is installed: node --version (requires v22+)"
echo " • Check inspector logs: cat logs/inspector.log"
echo " • Try manual install: npm install -g @modelcontextprotocol/inspector@0.16.5"
echo " • Manual start: DANGEROUSLY_OMIT_AUTH=true DEFAULT_TRANSPORT=streamable-http DEFAULT_SERVER_URL=http://localhost:$mcp_port/mcp/ npx @modelcontextprotocol/inspector@0.16.5"
fi
echo
print_status "📊 Log Files:"
echo " 📄 MCP Server: logs/mcp_server.log"
if [ "$inspector_available" = true ]; then
echo " 📄 MCP Inspector: logs/inspector.log"
fi
echo
print_status "🛑 To stop the server:"
echo " Press Ctrl+C or run: pkill -f 'fastmcp run'"
echo
print_local "Server is running in background. Use Ctrl+C to stop."
# Monitor both processes
while true; do
# Check if server is still running
if ! kill -0 $server_pid 2>/dev/null; then
print_error "MCP server process died unexpectedly!"
if [ -f logs/mcp_server.log ]; then
print_error "=== Recent server logs ==="
tail -10 logs/mcp_server.log
fi
break
fi
sleep 5
done
}
# Function to determine if Splunk should be run in Docker
should_run_splunk_docker() {
# Check if SPLUNK_HOST is not set or is set to 'so1' (our Docker Splunk container)
if [ -z "$SPLUNK_HOST" ] || [ "$SPLUNK_HOST" = "so1" ]; then
return 0 # true - run Splunk in Docker
else
return 1 # false - use external Splunk
fi
}
# Function to get Docker compose command
get_docker_compose_cmd() {
local mode=$1
local base_cmd="docker compose"
case $mode in
"dev")
base_cmd="docker compose -f docker-compose-dev.yml"
;;
"prod"|"")
base_cmd="docker compose"
;;
esac
echo "$base_cmd"
}
# Function to stop Docker services
stop_docker_services() {
print_status "Stopping Docker services..."
# Check if docker-compose is available
if ! command -v docker-compose &> /dev/null; then
print_error "docker-compose not found. Cannot stop services."
exit 1
fi
# Load environment variables from .env file for Docker Compose
if [ -f .env ]; then
load_env_file
fi
# Determine which profiles to use based on current configuration
local compose_cmd
compose_cmd=$(get_docker_compose_cmd "dev")
print_status "Using command: $compose_cmd down"
# Stop all services
if eval "$compose_cmd down"; then
print_success "All Docker services stopped successfully!"
# Check if there are any remaining resources
print_status "Checking for remaining resources..."
if docker network ls | grep -q "mcp-server-for-splunk"; then
print_warning "Some networks may still exist. You can remove them manually with:"
print_warning " docker network prune"
fi
if docker volume ls | grep -q "mcp-server-for-splunk"; then
print_warning "Some volumes may still exist. You can remove them manually with:"
print_warning " docker volume prune"
fi
else
print_error "Failed to stop some Docker services"
print_warning "You may need to stop them manually:"
print_warning " docker ps -a | grep mcp-server-for-splunk"
print_warning " docker stop <container_id>"
exit 1
fi
}
# Function to run Docker setup
run_docker_setup() {
print_status "Using Docker deployment mode..."
# Check if docker-compose is available
if ! command -v docker-compose &> /dev/null; then
print_error "docker-compose not found. Please install docker-compose or use local mode."
print_error "To install docker-compose: https://docs.docker.com/compose/install/"
echo
print_error "📚 For detailed installation instructions, see:"
print_error " docs/getting-started/installation.md#-docker-installation-optional---for-full-stack"
exit 1
fi
# If uv is available, ensure uv.lock is up to date for Docker build
if check_uv; then
print_status "uv detected. Ensuring uv.lock is up to date for Docker build..."
# Check if uv.lock exists and is current
if [ ! -f "uv.lock" ] || [ "pyproject.toml" -nt "uv.lock" ]; then
print_status "Updating uv.lock file..."
uv sync --dev
print_success "uv.lock updated successfully!"
else
print_status "uv.lock is already up to date."
fi
else
print_warning "uv not found. Docker will use existing uv.lock file (if present)."
if [ ! -f "uv.lock" ]; then
print_warning "No uv.lock file found. Docker build may fail."
print_warning "Consider installing uv to generate the lock file: curl -LsSf https://astral.sh/uv/install.sh | sh"
fi
fi
# Check if .env file exists
if [ ! -f .env ]; then
print_warning ".env file not found. Creating from env.example..."
cp env.example .env
print_warning "Created .env file. You may want to edit it with your Splunk configuration."
fi
# Prompt for Splunk configuration with Docker mode enabled
prompt_splunk_config true
# Load environment variables from .env file for Docker Compose
load_env_file
# Ask user for Docker deployment mode
echo
print_status "Choose Docker deployment mode:"
echo " 1) Production (default) - Optimized for performance, no hot reload"
echo " 2) Development - Hot reload enabled, enhanced debugging"
echo
read -p "Enter your choice (1 or 2, default: 1): " docker_choice
docker_choice=${docker_choice:-1}
local docker_mode=""
local service_name="mcp-server"
case $docker_choice in
1)
docker_mode="prod"
print_status "Using Production mode (optimized performance)"
;;
2)
docker_mode="dev"
service_name="mcp-server-dev"
print_status "Using Development mode (hot reload enabled)"
;;
*)
print_warning "Invalid choice. Using Production mode (default)."
docker_mode="prod"
;;
esac
# Get the appropriate docker-compose command
local compose_cmd
compose_cmd=$(get_docker_compose_cmd "$docker_mode")
# Show Splunk configuration info
if should_run_splunk_docker; then
print_local "Including Splunk Enterprise container (SPLUNK_HOST=${SPLUNK_HOST:-'not set'} indicates local Splunk)"
print_local "Using profiles: dev + splunk for complete stack"
else
print_local "Using external Splunk instance: $SPLUNK_HOST"
print_local "Using profile: dev only (no local Splunk)"
fi
print_status "Building Docker image..."
eval "$compose_cmd build $service_name"
if [ $? -eq 0 ]; then
print_success "Docker image built successfully!"
else
print_error "Failed to build Docker image"
exit 1
fi
print_status "Starting services with docker-compose..."
eval "$compose_cmd up -d"
if [ $? -eq 0 ]; then
print_success "Services started successfully!"
else
print_error "Failed to start services"
exit 1
fi
# Wait a moment for services to start
sleep 5
print_status "Checking service status..."
eval "$compose_cmd ps"
# Wait for MCP Inspector to be ready
print_status "Waiting for MCP Inspector to be ready..."
local inspector_ready=false
local inspector_attempts=0
local max_inspector_attempts=30
while [ $inspector_attempts -lt $max_inspector_attempts ] && [ "$inspector_ready" = false ]; do
if command -v curl &> /dev/null; then
if curl -s -f "http://localhost:6274" > /dev/null 2>&1; then
inspector_ready=true
print_success "MCP Inspector is ready and accessible at http://localhost:6274"
break
fi
fi
sleep 2
inspector_attempts=$((inspector_attempts + 1))
if [ $((inspector_attempts % 5)) -eq 0 ]; then
print_local "Waiting for MCP Inspector... (attempt $inspector_attempts/$max_inspector_attempts)"
fi
done
if [ "$inspector_ready" = false ]; then
print_warning "MCP Inspector may not be fully ready yet. Check logs:"
print_warning " $compose_cmd logs mcp-inspector"
fi
print_status "Checking MCP server logs..."
eval "$compose_cmd logs $service_name --tail=20"
echo
print_success "🎉 Docker setup complete!"
echo
print_status "📋 Service URLs:"
echo " 🔧 Traefik Dashboard: http://localhost:8080"
if should_run_splunk_docker; then
echo " 🌐 Splunk Web UI: http://localhost:9000 (admin/Chang3d!)"
else
echo " 🌐 External Splunk: $SPLUNK_HOST (configured in .env)"
fi
echo " 🔌 MCP Server: http://localhost:${MCP_SERVER_PORT:-8001}/mcp/"
echo " 📊 MCP Inspector: http://localhost:6274"
echo
print_status "🔍 To check logs:"
echo " $compose_cmd logs -f $service_name"
echo " $compose_cmd logs -f mcp-inspector"
echo
print_status "🛑 To stop all services:"
echo " $compose_cmd down"
if should_run_splunk_docker; then
echo " Note: Use '--remove-orphans' flag to ensure complete cleanup:"
echo " $compose_cmd down --remove-orphans"
fi
if [ "$docker_mode" = "dev" ]; then
echo
print_status "🚀 Development Mode Features:"
echo " • Hot reload enabled - changes sync automatically"
echo " • Enhanced debugging and logging"
echo " • Use: $compose_cmd logs -f $service_name"
fi
}
# Function to find an available port
find_available_port() {
local start_port=$1
local max_attempts=10
local port=$start_port
for ((i=0; i<max_attempts; i++)); do
local port_available=true
# Check if any process is actively listening on the port
if command -v lsof &> /dev/null; then
# Only check for LISTEN state, ignore CLOSED connections
if lsof -i :$port 2>/dev/null | grep -q "LISTEN"; then
port_available=false
fi
elif command -v netstat &> /dev/null; then
# netstat is more reliable - only shows LISTEN state
if netstat -an | grep ":$port " | grep -q "LISTEN"; then
port_available=false
fi
fi
# If port seems available, try a more direct test
if [ "$port_available" = true ]; then
# Try to bind to the port using Python (most reliable cross-platform)
if command -v python3 &> /dev/null; then
if python3 -c "import socket; s=socket.socket(); s.bind(('127.0.0.1', $port)); s.close()" 2>/dev/null; then
echo $port
return 0
else
port_available=false
fi
elif command -v nc &> /dev/null; then
# Fallback to nc if Python not available
# Try to connect first - if we can't connect, port is likely free
if ! nc -z -w 1 localhost $port 2>/dev/null; then
echo $port
return 0
else
port_available=false
fi
else
# Last resort: try /dev/tcp
if ! (echo >/dev/tcp/localhost/$port) 2>/dev/null; then
echo $port
return 0
else
port_available=false
fi
fi
fi
if [ "$port_available" = false ]; then
print_local "Port $port is in use, trying next port..." >&2
fi
port=$((port + 1))
done
# If we can't find a port, return the original
print_warning "Could not find an available port after $max_attempts attempts" >&2
echo $start_port
return 1
}
# Splunk configuration will be prompted after deployment mode is chosen
# Main logic: Check Docker availability and choose deployment method
print_status "Checking available deployment options..."
# Check if Docker is running
if docker info > /dev/null 2>&1; then
print_success "Docker is available and running."
DOCKER_AVAILABLE=true
else
print_warning "Docker is not available."
DOCKER_AVAILABLE=false
fi
# Check if uv is available
if check_uv; then
print_success "uv package manager is available."
UV_AVAILABLE=true
else
print_warning "uv package manager is not available."
UV_AVAILABLE=false
fi
# Handle forced deployment modes
if [ "$FORCE_MODE" = true ]; then
case $DEPLOYMENT_MODE in
"docker")
if [ "$DOCKER_AVAILABLE" = true ]; then
print_status "Forcing Docker deployment as requested..."
run_docker_setup
else
print_error "Docker deployment requested but Docker is not available."
print_error "Please start Docker or install Docker first."
exit 1
fi
;;
"local")
if [ "$UV_AVAILABLE" = true ]; then
print_status "Forcing local deployment as requested..."
setup_local_env
run_local_server
else
print_error "Local deployment requested but uv package manager is not available."
print_error "Please install uv first: curl -LsSf https://astral.sh/uv/install.sh | sh"
exit 1
fi
;;
esac
exit 0
fi
# Handle stop mode
if [ "$STOP_MODE" = true ]; then
if [ "$DOCKER_AVAILABLE" = true ]; then
stop_docker_services
else
print_error "Docker is not available. Cannot stop services."
exit 1
fi
exit 0
fi
# Interactive mode - ask user preference if both options are available
if [ "$DOCKER_AVAILABLE" = true ] && [ "$UV_AVAILABLE" = true ]; then
echo
print_status "Both Docker and local development options are available."
echo "Choose deployment method:"
echo " 1) Docker (full stack with Splunk, Traefik, MCP Inspector)"
echo " 2) Local (FastMCP server only, lighter weight)"
echo
read -p "Enter your choice (1 or 2, default: 1): " choice
choice=${choice:-1}
case $choice in
1)
run_docker_setup
;;
2)
setup_local_env
run_local_server
;;
*)
print_warning "Invalid choice. Using Docker deployment (default)."
run_docker_setup
;;
esac
elif [ "$DOCKER_AVAILABLE" = true ]; then
# Only Docker available, use Docker
print_status "Only Docker is available. Using Docker deployment..."
run_docker_setup
elif [ "$UV_AVAILABLE" = true ]; then
# Only uv available, use local mode
print_status "Only local development is available. Setting up local mode..."
setup_local_env
run_local_server
else
# Neither Docker nor uv available
print_error "Neither Docker nor uv package manager are available."
print_error "Please install one of the following:"
print_error "1. Docker: https://docs.docker.com/get-docker/"
print_error "2. uv: curl -LsSf https://astral.sh/uv/install.sh | sh"
echo
print_error "📚 For detailed installation instructions, see:"
print_error " docs/getting-started/installation.md"
print_error ""
print_error "🔧 You can also run our prerequisite checker to see what's missing:"
print_error " ./scripts/check-prerequisites.sh"
exit 1
fi