build_and_run.ps1โข42.3 kB
# Build and Run MCP Server for Splunk - Windows PowerShell Version
# This script builds the Docker image and runs it with docker-compose, or falls back to local mode
param(
[switch]$LocalOnly,
[switch]$DockerOnly,
[switch]$Help
)
# Set strict mode for better error handling
Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"
# Change to the project root directory (parent of scripts)
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
$projectRoot = Split-Path -Parent $scriptDir
Set-Location $projectRoot
# Show help if requested
if ($Help) {
Write-Host @"
MCP Server for Splunk - Build and Run Script (Windows)
Usage:
.\scripts\build_and_run.ps1 [options]
Options:
-LocalOnly Force local development mode (bypass Docker)
-DockerOnly Force Docker mode (fail if Docker unavailable)
-Help Show this help message
Examples:
.\scripts\build_and_run.ps1 # Auto-detect best mode
.\scripts\build_and_run.ps1 -LocalOnly # Use local development mode
.\scripts\build_and_run.ps1 -DockerOnly # Use Docker mode only
Prerequisites:
- Python 3.10+ (for local mode)
- uv package manager (auto-installed if missing)
- Docker Desktop (for Docker mode)
- Node.js/npm (optional, for MCP Inspector)
Configuration:
- Edit .env file with your Splunk credentials
- Or set MCP_SPLUNK_* environment variables
"@
exit 0
}
Write-Host "๐ Building and Running MCP Server for Splunk (Windows)" -ForegroundColor Cyan
Write-Host "=============================================" -ForegroundColor Cyan
Write-Host ""
Write-Host "๐ Need help with prerequisites? See: docs/getting-started/installation.md" -ForegroundColor Yellow
Write-Host ""
# Color functions for consistent output
function Write-Status {
param([string]$Message)
Write-Host "[INFO] $Message" -ForegroundColor Blue
}
function Write-Success {
param([string]$Message)
Write-Host "[SUCCESS] $Message" -ForegroundColor Green
}
function Write-Warning {
param([string]$Message)
Write-Host "[WARNING] $Message" -ForegroundColor Yellow
}
function Write-Error {
param([string]$Message)
Write-Host "[ERROR] $Message" -ForegroundColor Red
}
function Write-Local {
param([string]$Message)
Write-Host "[LOCAL] $Message" -ForegroundColor Magenta
}
# Function to ensure logs directory exists
function Ensure-LogsDir {
if (-not (Test-Path "logs")) {
Write-Local "Creating logs directory..."
New-Item -ItemType Directory -Path "logs" -Force | Out-Null
}
}
# Function to load environment variables from .env file
function Load-EnvFile {
if (Test-Path ".env") {
Write-Local "Loading environment variables from .env file..."
$envVars = @{}
$sensitiveVars = @("PASSWORD", "SECRET", "TOKEN")
Get-Content ".env" | ForEach-Object {
$line = $_.Trim()
# Skip empty lines and comments
if ($line -eq "" -or $line.StartsWith("#")) {
return
}
# Parse variable assignment
if ($line -match "^([A-Za-z_][A-Za-z0-9_]*)=(.*)$") {
$varName = $matches[1]
$varValue = $matches[2]
# Remove surrounding quotes if present
$varValue = $varValue -replace '^"(.*)"$', '$1' -replace "^'(.*)'$", '$1'
# Set environment variable
[Environment]::SetEnvironmentVariable($varName, $varValue, "Process")
$envVars[$varName] = $varValue
# Only show non-sensitive variables
$isSensitive = $sensitiveVars | Where-Object { $varName -like "*$_*" }
if ($isSensitive) {
Write-Local "Loaded: $varName=***"
} else {
Write-Local "Loaded: $varName=$varValue"
}
}
}
Write-Success "Environment variables loaded from .env file!"
# Show summary of important Splunk configuration
Write-Host ""
Write-Status "๐ Splunk Configuration Summary:"
Write-Host " ๐ Host: $($env:SPLUNK_HOST ?? 'Not set')"
Write-Host " ๐ Port: $($env:SPLUNK_PORT ?? '8089 (default)')"
Write-Host " ๐ค User: $($env:SPLUNK_USERNAME ?? 'Not set')"
# Show last 3 chars of password for verification
if ($env:SPLUNK_PASSWORD) {
$passDisplay = "***" + $env:SPLUNK_PASSWORD.Substring([Math]::Max(0, $env:SPLUNK_PASSWORD.Length - 3))
Write-Host " ๐ Pass: $passDisplay"
} else {
Write-Host " ๐ Pass: Not set"
}
Write-Host " ๐ SSL: $($env:SPLUNK_VERIFY_SSL ?? 'Not set')"
# Check for alternative MCP_SPLUNK_* variables
if ($env:MCP_SPLUNK_HOST -or $env:MCP_SPLUNK_USERNAME -or $env:MCP_SPLUNK_PASSWORD) {
Write-Host ""
Write-Status "๐ Alternative MCP Splunk Configuration Found:"
Write-Host " ๐ Host: $($env:MCP_SPLUNK_HOST ?? 'Not set')"
Write-Host " ๐ค User: $($env:MCP_SPLUNK_USERNAME ?? 'Not set')"
# Show last 3 chars of MCP password for verification
if ($env:MCP_SPLUNK_PASSWORD) {
$mcpPassDisplay = "***" + $env:MCP_SPLUNK_PASSWORD.Substring([Math]::Max(0, $env:MCP_SPLUNK_PASSWORD.Length - 3))
Write-Host " ๐ Pass: $mcpPassDisplay"
} else {
Write-Host " ๐ Pass: Not set"
}
Write-Local "These MCP_SPLUNK_* variables will override the SPLUNK_* variables in the MCP server."
}
Write-Host ""
} else {
Write-Warning "No .env file found. Using system environment variables only."
# Check if any Splunk environment variables are set in the system
if ($env:SPLUNK_HOST -or $env:MCP_SPLUNK_HOST) {
Write-Status "๐ System Environment Splunk Configuration:"
Write-Host " ๐ Host: $($env:SPLUNK_HOST ?? $env:MCP_SPLUNK_HOST ?? 'Not set')"
Write-Host " ๐ค User: $($env:SPLUNK_USERNAME ?? $env:MCP_SPLUNK_USERNAME ?? 'Not set')"
# Show last 3 chars of password for verification
$sysPassword = $env:SPLUNK_PASSWORD ?? $env:MCP_SPLUNK_PASSWORD
if ($sysPassword) {
$sysPassDisplay = "***" + $sysPassword.Substring([Math]::Max(0, $sysPassword.Length - 3))
Write-Host " ๐ Pass: $sysPassDisplay"
} else {
Write-Host " ๐ Pass: Not set"
}
} else {
Write-Warning "No Splunk configuration found in environment variables."
Write-Warning "The MCP server may not be able to connect to Splunk without configuration."
}
}
}
# Function to check if uv is installed
function Test-UV {
try {
$null = Get-Command "uv" -ErrorAction Stop
return $true
} catch {
return $false
}
}
# Function to install uv
function Install-UV {
Write-Status "Installing uv package manager..."
try {
# Use the official uv installation method for Windows
if (Get-Command "curl" -ErrorAction SilentlyContinue) {
$installer = Invoke-WebRequest -Uri "https://astral.sh/uv/install.ps1" -UseBasicParsing
Invoke-Expression $installer.Content
} elseif (Get-Command "winget" -ErrorAction SilentlyContinue) {
Write-Status "Using winget to install uv..."
winget install --id=astral-sh.uv --source=winget
} else {
Write-Status "Using pip to install uv..."
python -m pip install uv
}
# Refresh PATH to ensure uv is available
$env:PATH = [System.Environment]::GetEnvironmentVariable("PATH", "Machine") + ";" + [System.Environment]::GetEnvironmentVariable("PATH", "User")
# Verify installation
if (Test-UV) {
Write-Success "uv installed successfully!"
} else {
throw "uv installation verification failed"
}
} catch {
Write-Error "Failed to install uv. Please install manually:"
Write-Error " Option 1: pip install uv"
Write-Error " Option 2: Download from https://astral.sh/uv/"
Write-Error " Option 3: winget install astral-sh.uv"
Write-Host ""
Write-Error "๐ For detailed installation instructions, see:"
Write-Error " docs/getting-started/installation.md#-uv-package-manager-installation"
exit 1
}
}
# Function to setup local environment
function Setup-LocalEnv {
Write-Local "Setting up local development environment..."
# Check if uv is available
if (-not (Test-UV)) {
Write-Warning "uv not found. Installing uv..."
Install-UV
}
# Check if virtual environment and dependencies are installed
if (-not (Test-Path ".venv") -or -not (Test-Path ".venv/pyvenv.cfg")) {
Write-Local "Creating virtual environment and installing dependencies..."
& uv sync --dev
if ($LASTEXITCODE -ne 0) {
Write-Error "Failed to create virtual environment and install dependencies"
exit 1
}
} else {
Write-Local "Virtual environment exists. Checking if sync is needed..."
# Check if uv.lock is newer than .venv (Windows compatible)
$uvLockTime = (Get-Item "uv.lock" -ErrorAction SilentlyContinue)?.LastWriteTime
$pyprojectTime = (Get-Item "pyproject.toml" -ErrorAction SilentlyContinue)?.LastWriteTime
$venvTime = (Get-Item ".venv/pyvenv.cfg" -ErrorAction SilentlyContinue)?.LastWriteTime
if (($uvLockTime -and $venvTime -and $uvLockTime -gt $venvTime) -or
($pyprojectTime -and $venvTime -and $pyprojectTime -gt $venvTime)) {
Write-Local "Dependencies may be outdated. Running uv sync..."
& uv sync --dev
if ($LASTEXITCODE -ne 0) {
Write-Error "Failed to sync dependencies"
exit 1
}
} else {
Write-Local "Dependencies are up to date."
}
}
# Check if .env file exists
if (-not (Test-Path ".env")) {
Write-Warning ".env file not found. Creating from env.example..."
Copy-Item "env.example" ".env"
Write-Warning "Created .env file. You may want to edit it with your Splunk configuration."
Write-Warning "For local development, you can also use MCP_SPLUNK_* environment variables."
}
# Load environment variables from .env file
Load-EnvFile
Write-Success "Local environment setup complete!"
}
# Function to find an available port
function Find-AvailablePort {
param([int]$StartPort = 8001)
$maxAttempts = 10
$port = $StartPort
for ($i = 0; $i -lt $maxAttempts; $i++) {
$portAvailable = $true
# First check if anything is listening on the port
try {
$tcpConnections = Get-NetTCPConnection -LocalPort $port -State Listen -ErrorAction SilentlyContinue
if ($tcpConnections) {
$portAvailable = $false
}
} catch {
# If Get-NetTCPConnection isn't available, try TcpListener
try {
$listener = [System.Net.Sockets.TcpListener]::new([System.Net.IPAddress]::Any, $port)
$listener.Start()
$listener.Stop()
# If we got here, port is available
return $port
} catch {
$portAvailable = $false
}
}
if ($portAvailable) {
# Double-check by trying to bind
try {
$listener = [System.Net.Sockets.TcpListener]::new([System.Net.IPAddress]::Any, $port)
$listener.Start()
$listener.Stop()
return $port
} catch {
$portAvailable = $false
}
}
if (-not $portAvailable) {
Write-Local "Port $port is in use, trying next port..."
}
$port++
}
# If we can't find a port, return the original
Write-Warning "Could not find an available port after $maxAttempts attempts"
return $StartPort
}
# Function to test if a port is listening
function Test-PortListening {
param([int]$Port)
try {
$tcpClient = New-Object System.Net.Sockets.TcpClient
$tcpClient.ConnectAsync("localhost", $Port).Wait(1000)
$isListening = $tcpClient.Connected
$tcpClient.Close()
return $isListening
} catch {
return $false
}
}
# Function to run local server
function Start-LocalServer {
Write-Local "Starting MCP server locally with FastMCP CLI..."
# Check if Node.js/npm is available for MCP Inspector
$inspectorAvailable = $false
$inspectorSupported = $false
if (Get-Command "npx" -ErrorAction SilentlyContinue) {
$inspectorSupported = $true
Write-Local "Node.js/npx detected. MCP Inspector will start after MCP server is running..."
} else {
Write-Warning "Node.js/npx not found. MCP Inspector will not be available."
Write-Warning "To install Node.js: https://nodejs.org/"
Write-Host ""
Write-Warning "๐ For detailed installation instructions, see:"
Write-Warning " docs/getting-started/installation.md#-nodejs-installation-optional---for-mcp-inspector"
$inspectorSupported = $false
}
Write-Host ""
Write-Status "Starting MCP server..."
# Test if the FastMCP command works first
Write-Local "Testing FastMCP installation..."
try {
& uv run python -c "import fastmcp; print('FastMCP import successful')"
if ($LASTEXITCODE -ne 0) {
throw "FastMCP import failed"
}
} catch {
Write-Error "FastMCP import failed. Checking installation..."
Write-Local "Installing FastMCP..."
& uv add fastmcp
& uv sync --dev
if ($LASTEXITCODE -ne 0) {
Write-Error "Failed to install FastMCP"
exit 1
}
}
# Start the server with uv and better error handling
Write-Local "Finding available port for MCP server..."
# Check for MCP_SERVER_PORT environment variable, default to 8001
$preferredPort = if ($env:MCP_SERVER_PORT) { [int]$env:MCP_SERVER_PORT } else { 8001 }
Write-Local "Preferred port from MCP_SERVER_PORT: $preferredPort"
# Start from preferred port to avoid conflict with Splunk Web UI (port 8000)
$mcpPort = Find-AvailablePort -StartPort $preferredPort
if ($mcpPort -ne $preferredPort) {
Write-Warning "Port $preferredPort is in use. Using port $mcpPort instead."
} else {
Write-Local "Using port $mcpPort for MCP server."
}
Write-Local "Starting MCP server on port $mcpPort..."
Write-Local "Command: uv run fastmcp run src/server.py --transport http --port $mcpPort"
# Ensure logs directory exists
Ensure-LogsDir
# Start server in background to check if it starts successfully
try {
$serverProcess = Start-Process -FilePath "uv" -ArgumentList "run", "fastmcp", "run", "src/server.py", "--transport", "http", "--port", $mcpPort -RedirectStandardOutput "logs/mcp_server.log" -RedirectStandardError "logs/mcp_server_error.log" -PassThru -NoNewWindow
# Give server time to start
Write-Local "Waiting for MCP server to start..."
Start-Sleep -Seconds 3
# Check if the server process is still running
if ($serverProcess.HasExited) {
Write-Error "MCP server failed to start. Check the logs:"
Write-Host ""
if (Test-Path "logs/mcp_server.log") {
Write-Error "=== MCP Server Log ==="
Get-Content "logs/mcp_server.log" | Write-Host
Write-Host ""
}
if (Test-Path "logs/mcp_server_error.log") {
Write-Error "=== MCP Server Error Log ==="
Get-Content "logs/mcp_server_error.log" | Write-Host
Write-Host ""
}
Write-Error "Troubleshooting steps:"
Write-Host "1. Check if src/server.py exists and is valid"
Write-Host "2. Verify FastMCP installation: uv run python -c 'import fastmcp'"
Write-Host "3. Try running manually: uv run fastmcp run src/server.py --help"
Write-Host "4. Check Python environment: uv run python --version"
Write-Host ""
Write-Error "๐ For prerequisite installation help, see:"
Write-Error " docs/getting-started/installation.md"
Write-Host ""
Write-Error "๐ง Run the prerequisite checker to see what's missing:"
Write-Error " .\scripts\check-prerequisites.ps1"
Stop-AllProcesses
exit 1
}
# Check if the chosen port is actually listening
Write-Local "Checking if MCP server is listening on port $mcpPort..."
$portCheckAttempts = 0
$maxPortAttempts = 5
$serverListening = $false
while ($portCheckAttempts -lt $maxPortAttempts) {
if (Test-PortListening -Port $mcpPort) {
$serverListening = $true
break
}
Start-Sleep -Seconds 2
$portCheckAttempts++
Write-Local "Port check attempt $portCheckAttempts/$maxPortAttempts..."
}
if ($serverListening) {
Write-Success "MCP server is listening on port $mcpPort!"
# Also test basic connectivity
try {
$response = Invoke-WebRequest -Uri "http://localhost:$mcpPort" -TimeoutSec 5 -UseBasicParsing
Write-Success "Server responds with HTTP $($response.StatusCode)"
} catch {
Write-Warning "Server running but HTTP request failed: $_"
}
# Start MCP Inspector now that the actual port is known
if ($inspectorSupported) {
Write-Status "Starting MCP Inspector..."
if (Test-PortListening -Port 6274) {
Write-Warning "MCP Inspector appears to already be running on port 6274"
Write-Success "Using existing MCP Inspector instance"
$inspectorAvailable = $true
} else {
# Configure environment for inspector (env vars, not CLI flags)
$env:DANGEROUSLY_OMIT_AUTH = "true"
$env:MCP_AUTO_OPEN_ENABLED = "false"
# Use command-line arguments for more reliable configuration
$inspectorUrl = "http://localhost:$mcpPort/mcp/"
Write-Local "Configuring MCP Inspector to connect to: $inspectorUrl"
Ensure-LogsDir
try {
$inspectorProcess = Start-Process -FilePath "npx" -ArgumentList "--yes", "@modelcontextprotocol/inspector", "--transport", "streamable-http", "--server-url", $inspectorUrl -RedirectStandardOutput "logs/inspector.log" -RedirectStandardError "logs/inspector_error.log" -PassThru -NoNewWindow
Write-Local "Waiting for MCP Inspector to start..."
$attempts = 0
$maxAttempts = 10
$inspectorRunning = $false
while ($attempts -lt $maxAttempts) {
Start-Sleep -Seconds 2
if ($inspectorProcess.HasExited) {
Write-Error "MCP Inspector process died. Check logs/inspector.log for details."
break
}
if (Test-PortListening -Port 6274) {
$inspectorRunning = $true
break
}
$attempts++
Write-Local "Attempt $attempts/$maxAttempts - waiting for inspector..."
}
if ($inspectorRunning) {
Write-Success "MCP Inspector started successfully on port 6274"
$inspectorProcess.Id | Out-File ".inspector_pid" -Encoding ASCII
$inspectorAvailable = $true
} else {
Write-Warning "Failed to start MCP Inspector"
if (Test-Path "logs/inspector.log") {
Write-Warning "Inspector log (last 10 lines):"
Get-Content "logs/inspector.log" -Tail 10 | Write-Host
}
if (-not $inspectorProcess.HasExited) { $inspectorProcess.Kill() }
$inspectorAvailable = $false
}
} catch {
Write-Warning "Failed to start MCP Inspector: $_"
$inspectorAvailable = $false
}
}
}
} else {
Write-Error "MCP server is not listening on port $mcpPort"
Write-Error "Server process ID: $($serverProcess.Id)"
# Show server logs for debugging
if (Test-Path "logs/mcp_server.log") {
Write-Error "=== MCP Server Log (last 20 lines) ==="
Get-Content "logs/mcp_server.log" -Tail 20 | Write-Host
Write-Host ""
}
Write-Error "Attempting to restart server in foreground for debugging..."
$serverProcess.Kill()
Start-Sleep -Seconds 2
Write-Local "Running server in foreground mode for debugging..."
Write-Local "If this works, there might be a background process issue..."
# Try running in foreground (this will block)
& uv run fastmcp run src/server.py --transport http --port $mcpPort
exit 1
}
# Save server PID for cleanup
$serverProcess.Id | Out-File ".server_pid" -Encoding ASCII
# Show final summary with access points
Write-Host ""
Write-Success "โ
MCP Server setup complete!"
Write-Host ""
Write-Status "๐ Local MCP Server Ready!"
Write-Host ""
Write-Status "๐ Access Points:"
Write-Host " ๐ MCP Server (stdio): Available for MCP clients"
Write-Host " ๐ MCP Server (HTTP): http://localhost:$mcpPort"
if ($inspectorSupported) {
Write-Host " ๐ MCP Inspector: http://localhost:6274"
Write-Host ""
Write-Status "๐ฏ Testing Instructions:"
Write-Host " 1. Open http://localhost:6274 in your browser"
Write-Host " 2. Ensure that Streamable HTTP is set"
Write-Host " 3. MCP Inspector is pre-configured to connect to port $mcpPort"
Write-Host " 4. Click 'Connect' button at the bottom"
Write-Host " 5. Test tools and resources interactively"
} else {
Write-Host ""
Write-Status "๐ฏ Alternative Testing:"
Write-Host " 1. Open https://inspector.mcp.dev/ in your browser"
Write-Host " 2. Ensure that Streamable HTTP is set"
Write-Host " 3. Enter server URL: http://localhost:$mcpPort/mcp/"
Write-Host " 4. Click 'Connect' button at the bottom"
Write-Host " 5. Test tools and resources interactively"
Write-Host ""
Write-Warning "๐ก MCP Inspector Troubleshooting:"
Write-Host " โข Check if Node.js is installed: node --version"
Write-Host " โข Check inspector logs: Get-Content logs/inspector.log"
Write-Host " โข Try manual install: npm install -g @modelcontextprotocol/inspector"
Write-Host " โข Manual start: `$env:DANGEROUSLY_OMIT_AUTH='true'; `$env:DEFAULT_TRANSPORT='streamable-http'; `$env:DEFAULT_SERVER_URL='http://localhost:$mcpPort/mcp/'; npx @modelcontextprotocol/inspector"
}
Write-Host ""
Write-Status "๐ Log Files:"
Write-Host " ๐ MCP Server: logs/mcp_server.log"
if ($inspectorSupported) {
Write-Host " ๐ MCP Inspector: logs/inspector.log"
}
Write-Host ""
Write-Status "๐ To stop the server:"
Write-Host " Press Ctrl+C or run Stop-Process -Name 'uv'"
Write-Host ""
Write-Local "Server is running in background. Use Ctrl+C to stop."
# Monitor both processes
try {
while ($true) {
# Check if server is still running
if ($serverProcess.HasExited) {
Write-Error "MCP server process died unexpectedly!"
if (Test-Path "logs/mcp_server.log") {
Write-Error "=== Recent server logs ==="
Get-Content "logs/mcp_server.log" -Tail 10 | Write-Host
}
break
}
Start-Sleep -Seconds 5
}
} finally {
Stop-AllProcesses
}
} catch {
Write-Error "Failed to start MCP server: $_"
Stop-AllProcesses
exit 1
}
}
# Function to determine if Splunk should be run in Docker
function Test-ShouldRunSplunkDocker {
# Check if SPLUNK_HOST is not set or is set to 'so1' (our Docker Splunk container)
if (-not $env:SPLUNK_HOST -or $env:SPLUNK_HOST -eq "so1") {
return $true # run Splunk in Docker
} else {
return $false # use external Splunk
}
}
# Function to get Docker compose command with profiles
function Get-DockerComposeCmd {
param([string]$Mode)
$baseCmd = "docker-compose"
switch ($Mode) {
"dev" {
$baseCmd = "docker-compose -f docker-compose-dev.yml"
}
default {
$baseCmd = "docker-compose"
}
}
# Add Splunk profile if needed
if (Test-ShouldRunSplunkDocker) {
if ($Mode -eq "dev") {
$baseCmd = "$baseCmd --profile dev --profile splunk"
} else {
$baseCmd = "$baseCmd --profile splunk"
}
Write-Local "Including Splunk Enterprise container (SPLUNK_HOST=$($env:SPLUNK_HOST ?? 'not set') indicates local Splunk)"
} else {
Write-Local "Using external Splunk instance: $env:SPLUNK_HOST"
}
return $baseCmd
}
# Function to run Docker setup
function Start-DockerSetup {
Write-Status "Using Docker deployment mode..."
# Check if docker-compose is available
if (-not (Get-Command "docker-compose" -ErrorAction SilentlyContinue)) {
Write-Error "docker-compose not found. Please install docker-compose or use local mode."
Write-Error "To install docker-compose: https://docs.docker.com/compose/install/"
Write-Host ""
Write-Error "๐ For detailed installation instructions, see:"
Write-Error " docs/getting-started/installation.md#-docker-installation-optional---for-full-stack"
exit 1
}
# If uv is available, ensure uv.lock is up to date for Docker build
if (Test-UV) {
Write-Status "uv detected. Ensuring uv.lock is up to date for Docker build..."
# Check if uv.lock exists and is current
$uvLockTime = (Get-Item "uv.lock" -ErrorAction SilentlyContinue)?.LastWriteTime
$pyprojectTime = (Get-Item "pyproject.toml" -ErrorAction SilentlyContinue)?.LastWriteTime
if (-not (Test-Path "uv.lock") -or ($pyprojectTime -and $uvLockTime -and $pyprojectTime -gt $uvLockTime)) {
Write-Status "Updating uv.lock file..."
& uv sync --dev
if ($LASTEXITCODE -eq 0) {
Write-Success "uv.lock updated successfully!"
} else {
Write-Error "Failed to update uv.lock file"
exit 1
}
} else {
Write-Status "uv.lock is already up to date."
}
} else {
Write-Warning "uv not found. Docker will use existing uv.lock file (if present)."
if (-not (Test-Path "uv.lock")) {
Write-Warning "No uv.lock file found. Docker build may fail."
Write-Warning "Consider installing uv to generate the lock file: https://astral.sh/uv/"
}
}
# Check if .env file exists
if (-not (Test-Path ".env")) {
Write-Warning ".env file not found. Creating from env.example..."
Copy-Item "env.example" ".env"
Write-Warning "Created .env file. You may want to edit it with your Splunk configuration."
}
# Load environment variables from .env file for Docker Compose
Load-EnvFile
# Ask user for Docker deployment mode
Write-Host ""
Write-Status "Choose Docker deployment mode:"
Write-Host " 1) Production (default) - Optimized for performance, no hot reload"
Write-Host " 2) Development - Hot reload enabled, enhanced debugging"
Write-Host ""
do {
$dockerChoice = Read-Host "Enter your choice (1 or 2, default: 1)"
if ([string]::IsNullOrEmpty($dockerChoice)) { $dockerChoice = "1" }
} while ($dockerChoice -notin @("1", "2"))
$dockerMode = ""
$serviceName = "mcp-server"
switch ($dockerChoice) {
"1" {
$dockerMode = "prod"
Write-Status "Using Production mode (optimized performance)"
}
"2" {
$dockerMode = "dev"
$serviceName = "mcp-server-dev"
Write-Status "Using Development mode (hot reload enabled)"
}
}
# Get the appropriate docker-compose command
$composeCmd = Get-DockerComposeCmd -Mode $dockerMode
Write-Status "Building Docker image..."
$buildCmd = "$composeCmd build $serviceName"
Invoke-Expression $buildCmd
if ($LASTEXITCODE -eq 0) {
Write-Success "Docker image built successfully!"
} else {
Write-Error "Failed to build Docker image"
exit 1
}
Write-Status "Starting services with docker-compose..."
$upCmd = "$composeCmd up -d"
Invoke-Expression $upCmd
if ($LASTEXITCODE -eq 0) {
Write-Success "Services started successfully!"
} else {
Write-Error "Failed to start services"
exit 1
}
# Wait a moment for services to start
Start-Sleep -Seconds 5
Write-Status "Checking service status..."
$psCmd = "$composeCmd ps"
Invoke-Expression $psCmd
Write-Status "Checking MCP server logs..."
$logsCmd = "$composeCmd logs $serviceName --tail=20"
Invoke-Expression $logsCmd
Write-Host ""
Write-Success "๐ Docker setup complete!"
Write-Host ""
Write-Status "๐ Service URLs:"
Write-Host " ๐ง Traefik Dashboard: http://localhost:8080"
if (Test-ShouldRunSplunkDocker) {
Write-Host " ๐ Splunk Web UI: http://localhost:9000 (admin/Chang3d!)"
} else {
Write-Host " ๐ External Splunk: $env:SPLUNK_HOST (configured in .env)"
}
Write-Host " ๐ MCP Server: http://localhost:8001/mcp/"
Write-Host " ๐ MCP Inspector: http://localhost:6274"
Write-Host ""
Write-Status "๐ To check logs:"
Write-Host " $composeCmd logs -f $serviceName"
Write-Host ""
Write-Status "๐ To stop all services:"
Write-Host " $composeCmd down"
if ($dockerMode -eq "dev") {
Write-Host ""
Write-Status "๐ Development Mode Features:"
Write-Host " โข Hot reload enabled - changes sync automatically"
Write-Host " โข Enhanced debugging and logging"
Write-Host " โข Use: $composeCmd logs -f $serviceName"
}
}
# Function to check if Docker is running
function Test-Docker {
try {
& docker info 2>&1 | Out-Null
return ($LASTEXITCODE -eq 0)
} catch {
return $false
}
}
# Function to stop all processes
function Stop-AllProcesses {
Write-Local "Shutting down services..."
# Kill MCP Server if it was started by this script
if (Test-Path ".server_pid") {
$serverPid = Get-Content ".server_pid" -ErrorAction SilentlyContinue
if ($serverPid) {
try {
$serverProcess = Get-Process -Id $serverPid -ErrorAction SilentlyContinue
if ($serverProcess) {
Write-Local "Stopping MCP Server (PID: $serverPid)..."
Stop-Process -Id $serverPid -Force -ErrorAction SilentlyContinue
}
} catch {
# Process might already be stopped
}
}
Remove-Item ".server_pid" -ErrorAction SilentlyContinue
}
# Kill MCP Inspector if it was started by this script
if (Test-Path ".inspector_pid") {
$inspectorPid = Get-Content ".inspector_pid" -ErrorAction SilentlyContinue
if ($inspectorPid) {
try {
$inspectorProcess = Get-Process -Id $inspectorPid -ErrorAction SilentlyContinue
if ($inspectorProcess) {
Write-Local "Stopping MCP Inspector (PID: $inspectorPid)..."
Stop-Process -Id $inspectorPid -Force -ErrorAction SilentlyContinue
}
} catch {
# Process might already be stopped
}
}
Remove-Item ".inspector_pid" -ErrorAction SilentlyContinue
}
# Clean up log files
Remove-Item "logs/inspector.log", "logs/inspector_error.log", "logs/mcp_server.log", "logs/mcp_server_error.log" -ErrorAction SilentlyContinue
Write-Success "Cleanup complete!"
}
# Set up signal handling for cleanup
$null = Register-EngineEvent -SourceIdentifier PowerShell.Exiting -Action {
Stop-AllProcesses
}
# Function to prompt for Splunk configuration and update .env file
function Prompt-SplunkConfig {
Write-Host ""
Write-Status "๐ง Splunk Configuration Setup"
Write-Host "=================================="
Write-Host ""
# Check if .env file exists, create from example if not
if (-not (Test-Path ".env")) {
if (Test-Path "env.example") {
Write-Local "Creating .env file from env.example..."
Copy-Item "env.example" ".env"
Write-Success ".env file created from env.example"
} else {
Write-Error "env.example not found. Cannot create .env file."
return $false
}
}
# Read current values from .env file
$currentHost = ""
$currentPort = "8089"
$currentUsername = "admin"
$currentPassword = ""
if (Test-Path ".env") {
$envContent = Get-Content ".env"
foreach ($line in $envContent) {
if ($line -match "^SPLUNK_HOST=(.+)$") {
$currentHost = $matches[1].Trim('"', "'")
} elseif ($line -match "^SPLUNK_PORT=(.+)$") {
$currentPort = $matches[1].Trim('"', "'")
} elseif ($line -match "^SPLUNK_USERNAME=(.+)$") {
$currentUsername = $matches[1].Trim('"', "'")
} elseif ($line -match "^SPLUNK_PASSWORD=(.+)$") {
$currentPassword = $matches[1].Trim('"', "'")
}
}
}
Write-Host "Current Splunk configuration:"
Write-Host " Host: $($currentHost ? $currentHost : 'Not set')"
Write-Host " Port: $currentPort"
Write-Host " Username: $currentUsername"
Write-Host " Password: $($currentPassword ? '***' : 'Not set')"
Write-Host ""
# Prompt for new values
$newHost = Read-Host "Enter Splunk host/URL (current: $($currentHost ? $currentHost : 'Not set'), press Enter to keep)"
$newPort = Read-Host "Enter Splunk port (current: $currentPort, press Enter to keep)"
$newUsername = Read-Host "Enter Splunk username (current: $currentUsername, press Enter to keep)"
$newPassword = Read-Host "Enter Splunk password (press Enter to keep current)" -AsSecureString
# Use new values if provided, otherwise keep current
$finalHost = if ($newHost) { $newHost } else { $currentHost }
$finalPort = if ($newPort) { $newPort } else { $currentPort }
$finalUsername = if ($newUsername) { $newUsername } else { $currentUsername }
$finalPassword = if ($newPassword.Length -gt 0) {
$BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($newPassword)
[System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR)
} else { $currentPassword }
# Parse URL if provided (strip protocol and extract hostname)
if ($newHost) {
if ($newHost -match "^https?://(.+)$") {
$finalHost = $matches[1]
Write-Local "Extracted hostname from URL: $finalHost"
# Set SSL verification based on protocol
if ($newHost -match "^https://") {
$env:SPLUNK_VERIFY_SSL = "true"
Write-Local "HTTPS detected - setting SPLUNK_VERIFY_SSL=true"
} else {
$env:SPLUNK_VERIFY_SSL = "false"
Write-Local "HTTP detected - setting SPLUNK_VERIFY_SSL=false"
}
}
}
# Validate required fields
if (-not $finalHost) {
Write-Error "SPLUNK_HOST is required. Please provide a value."
return $false
}
if (-not $finalUsername) {
Write-Error "SPLUNK_USERNAME is required. Please provide a value."
return $false
}
if (-not $finalPassword) {
Write-Error "SPLUNK_PASSWORD is required. Please provide a value."
return $false
}
# Show final configuration for confirmation
Write-Host ""
Write-Host "Final Splunk configuration:"
Write-Host " Host: $finalHost"
Write-Host " Port: $finalPort"
Write-Host " Username: $finalUsername"
Write-Host " Password: ***"
Write-Host ""
$confirm = Read-Host "Update .env file with these settings? (y/N)"
if ($confirm -match "^[Yy]$") {
# Update .env file
$tempEnv = ".env.tmp"
$envContent = Get-Content ".env"
# Process .env file line by line
foreach ($line in $envContent) {
if ($line -match "^SPLUNK_HOST=") {
"SPLUNK_HOST=$finalHost" | Out-File -FilePath $tempEnv -Append -Encoding ASCII
} elseif ($line -match "^SPLUNK_PORT=") {
"SPLUNK_PORT=$finalPort" | Out-File -FilePath $tempEnv -Append -Encoding ASCII
} elseif ($line -match "^SPLUNK_USERNAME=") {
"SPLUNK_USERNAME=$finalUsername" | Out-File -FilePath $tempEnv -Append -Encoding ASCII
} elseif ($line -match "^SPLUNK_PASSWORD=") {
"SPLUNK_PASSWORD=$finalPassword" | Out-File -FilePath $tempEnv -Append -Encoding ASCII
} else {
$line | Out-File -FilePath $tempEnv -Append -Encoding ASCII
}
}
# Replace original .env with updated version
Move-Item $tempEnv ".env" -Force
Write-Success ".env file updated successfully!"
# Set the new values for current session
$env:SPLUNK_HOST = $finalHost
$env:SPLUNK_PORT = $finalPort
$env:SPLUNK_USERNAME = $finalUsername
$env:SPLUNK_PASSWORD = $finalPassword
return $true
} else {
Write-Warning "Configuration update cancelled."
return $false
}
}
# Prompt for Splunk configuration before proceeding
Prompt-SplunkConfig
# Main logic: Check Docker availability and choose deployment method
try {
Write-Status "Checking available deployment options..."
# Handle command line parameters
if ($DockerOnly) {
if (Test-Docker) {
Start-DockerSetup
} else {
Write-Error "Docker mode requested but Docker is not available"
exit 1
}
} elseif ($LocalOnly) {
Setup-LocalEnv
Start-LocalServer
} else {
# Auto-detect best mode
if (Test-Docker) {
Write-Success "Docker is available and running."
# Ask user preference if both Docker and uv are available
if (Test-UV) {
Write-Host ""
Write-Status "Both Docker and local development options are available."
Write-Host "Choose deployment method:"
Write-Host " 1) Docker (full stack with Splunk, Traefik, MCP Inspector)"
Write-Host " 2) Local (FastMCP server only, lighter weight)"
Write-Host ""
do {
$choice = Read-Host "Enter your choice (1 or 2, default: 1)"
if ([string]::IsNullOrEmpty($choice)) { $choice = "1" }
} while ($choice -notin @("1", "2"))
switch ($choice) {
"1" { Start-DockerSetup }
"2" {
Setup-LocalEnv
Start-LocalServer
}
}
} else {
# Docker available but no uv, use Docker
Start-DockerSetup
}
} elseif (Test-UV) {
# No Docker but uv available, use local
Write-Warning "Docker is not available. Setting up local development mode..."
Setup-LocalEnv
Start-LocalServer
} else {
# Neither Docker nor uv available
Write-Warning "Neither Docker nor uv is available."
Write-Host ""
Write-Warning "๐ For detailed installation instructions, see:"
Write-Warning " docs/getting-started/installation.md"
Write-Host ""
Write-Warning "๐ง You can also run our prerequisite checker to see what's missing:"
Write-Warning " .\scripts\check-prerequisites.ps1"
Write-Host ""
Write-Status "Attempting to install uv automatically..."
Install-UV
Setup-LocalEnv
Start-LocalServer
}
}
} catch {
Write-Error "An error occurred: $_"
Stop-AllProcesses
exit 1
}