230 lines
7.7 KiB
PowerShell
230 lines
7.7 KiB
PowerShell
<#
|
|
.SYNOPSIS
|
|
Non-blocking server management script for local-mcp.
|
|
|
|
.DESCRIPTION
|
|
Manages the local-mcp server process without blocking the terminal.
|
|
The server runs as a detached background process; stdout/stderr are
|
|
written to logs/server.log.
|
|
|
|
.USAGE
|
|
.\server.ps1 start # Start (no-op if already running)
|
|
.\server.ps1 stop # Kill the running server
|
|
.\server.ps1 restart # Stop then start
|
|
.\server.ps1 status # Show PID, port state, and tail 20 log lines
|
|
.\server.ps1 logs [N] # Tail last N lines of the log (default 40)
|
|
.\server.ps1 logs -f # Follow log live (Ctrl-C to quit)
|
|
#>
|
|
|
|
param(
|
|
[Parameter(Position = 0)]
|
|
[ValidateSet("start", "stop", "restart", "status", "logs")]
|
|
[string]$Command = "status",
|
|
|
|
[Parameter(Position = 1)]
|
|
[string]$Arg = ""
|
|
)
|
|
|
|
# ── Paths ─────────────────────────────────────────────────────────────────
|
|
|
|
$Root = $PSScriptRoot
|
|
$Python = Join-Path $Root ".venv\Scripts\python.exe"
|
|
$Entry = Join-Path $Root "main.py"
|
|
$LogDir = Join-Path $Root "logs"
|
|
$LogOut = Join-Path $LogDir "server.log"
|
|
$LogErr = Join-Path $LogDir "server.err.log"
|
|
$PidFile = Join-Path $LogDir "server.pid"
|
|
$Port = 8000
|
|
|
|
# ── Helpers ───────────────────────────────────────────────────────────────
|
|
|
|
function EnsureLogDir {
|
|
if (-not (Test-Path $LogDir)) {
|
|
New-Item -ItemType Directory -Path $LogDir | Out-Null
|
|
}
|
|
}
|
|
|
|
function GetServerPid {
|
|
# Trust the PID file first; verify the process is actually alive
|
|
if (Test-Path $PidFile) {
|
|
$stored = Get-Content $PidFile -ErrorAction SilentlyContinue
|
|
if ($stored -match '^\d+$') {
|
|
$proc = Get-Process -Id ([int]$stored) -ErrorAction SilentlyContinue
|
|
if ($proc) { return [int]$stored }
|
|
}
|
|
Remove-Item $PidFile -ErrorAction SilentlyContinue
|
|
}
|
|
|
|
# Fallback: find python process listening on the port
|
|
$conn = Get-NetTCPConnection -LocalPort $Port -State Listen -ErrorAction SilentlyContinue |
|
|
Select-Object -First 1
|
|
if ($conn) { return $conn.OwningProcess }
|
|
|
|
return $null
|
|
}
|
|
|
|
function IsRunning {
|
|
return $null -ne (GetServerPid)
|
|
}
|
|
|
|
function WriteLog([string]$msg, [string]$color = "White") {
|
|
Write-Host $msg -ForegroundColor $color
|
|
}
|
|
|
|
function MergedLogTail([int]$n) {
|
|
# Merge stdout + stderr logs sorted by content order and tail n lines
|
|
$lines = @()
|
|
if (Test-Path $LogOut) { $lines += Get-Content $LogOut -Tail ($n * 2) }
|
|
if (Test-Path $LogErr) { $lines += Get-Content $LogErr -Tail ($n * 2) }
|
|
# Return last $n lines (simple approach — interleaving is approximate)
|
|
$lines | Select-Object -Last $n
|
|
}
|
|
|
|
# ── Commands ──────────────────────────────────────────────────────────────
|
|
|
|
function Start-Server {
|
|
if (IsRunning) {
|
|
$pid_ = GetServerPid
|
|
WriteLog "Server already running (PID $pid_ http://localhost:$Port)" "Green"
|
|
return
|
|
}
|
|
|
|
if (-not (Test-Path $Python)) {
|
|
WriteLog "ERROR: Python venv not found at $Python" "Red"
|
|
WriteLog "Run: python -m venv .venv && .venv\Scripts\pip install -r requirements.txt" "Yellow"
|
|
exit 1
|
|
}
|
|
|
|
EnsureLogDir
|
|
# Stamp both log files so they exist and have a separator
|
|
$stamp = "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') [server.ps1] ---- starting ----"
|
|
Add-Content $LogOut $stamp
|
|
Add-Content $LogErr $stamp
|
|
|
|
# Start python with stdout -> LogOut, stderr -> LogErr (separate files required on Windows)
|
|
$proc = Start-Process `
|
|
-FilePath $Python `
|
|
-ArgumentList "-u `"$Entry`"" `
|
|
-WorkingDirectory $Root `
|
|
-RedirectStandardOutput $LogOut `
|
|
-RedirectStandardError $LogErr `
|
|
-WindowStyle Hidden `
|
|
-PassThru
|
|
|
|
$proc.Id | Set-Content $PidFile
|
|
|
|
# Wait up to 6 s for the port to open
|
|
$deadline = (Get-Date).AddSeconds(6)
|
|
$ready = $false
|
|
while ((Get-Date) -lt $deadline) {
|
|
Start-Sleep -Milliseconds 400
|
|
$conn = Get-NetTCPConnection -LocalPort $Port -State Listen -ErrorAction SilentlyContinue
|
|
if ($conn) { $ready = $true; break }
|
|
}
|
|
|
|
if ($ready) {
|
|
WriteLog "Server started (PID $($proc.Id) http://localhost:$Port)" "Green"
|
|
} else {
|
|
WriteLog "Server process launched (PID $($proc.Id)) but port $Port not yet open." "Yellow"
|
|
WriteLog "Check logs: .\server.ps1 logs" "Yellow"
|
|
}
|
|
}
|
|
|
|
function Stop-Server {
|
|
$pid_ = GetServerPid
|
|
if (-not $pid_) {
|
|
WriteLog "Server is not running." "Yellow"
|
|
return
|
|
}
|
|
|
|
Stop-Process -Id $pid_ -Force -ErrorAction SilentlyContinue
|
|
Remove-Item $PidFile -ErrorAction SilentlyContinue
|
|
|
|
# Wait up to 4 s for the port to free
|
|
$deadline = (Get-Date).AddSeconds(4)
|
|
while ((Get-Date) -lt $deadline) {
|
|
Start-Sleep -Milliseconds 300
|
|
$conn = Get-NetTCPConnection -LocalPort $Port -State Listen -ErrorAction SilentlyContinue
|
|
if (-not $conn) { break }
|
|
}
|
|
|
|
WriteLog "Server stopped (was PID $pid_)" "Yellow"
|
|
}
|
|
|
|
function Restart-Server {
|
|
Stop-Server
|
|
Start-Sleep -Milliseconds 500
|
|
Start-Server
|
|
}
|
|
|
|
function Show-Status {
|
|
$pid_ = GetServerPid
|
|
if ($pid_) {
|
|
$proc = Get-Process -Id $pid_ -ErrorAction SilentlyContinue
|
|
$mem = if ($proc) { "{0:N0} MB" -f ($proc.WorkingSet64 / 1MB) } else { "?" }
|
|
WriteLog ""
|
|
WriteLog " Status : RUNNING" "Green"
|
|
WriteLog " PID : $pid_"
|
|
WriteLog " Memory : $mem"
|
|
WriteLog " URL : http://localhost:$Port"
|
|
WriteLog " Logs : $LogOut"
|
|
WriteLog " $LogErr"
|
|
} else {
|
|
WriteLog ""
|
|
WriteLog " Status : STOPPED" "Red"
|
|
}
|
|
|
|
WriteLog ""
|
|
WriteLog "--- Last 20 log lines (stdout) ---" "DarkGray"
|
|
if (Test-Path $LogOut) {
|
|
Get-Content $LogOut -Tail 20 | ForEach-Object { WriteLog $_ "DarkGray" }
|
|
} else {
|
|
WriteLog " (no log file yet)" "DarkGray"
|
|
}
|
|
|
|
if (Test-Path $LogErr) {
|
|
$errLines = Get-Content $LogErr -Tail 5 | Where-Object { $_ -match "ERROR|Exception|Traceback" }
|
|
if ($errLines) {
|
|
WriteLog ""
|
|
WriteLog "--- Recent errors (stderr) ---" "Red"
|
|
$errLines | ForEach-Object { WriteLog $_ "Red" }
|
|
}
|
|
}
|
|
WriteLog ""
|
|
}
|
|
|
|
function Tail-Logs {
|
|
EnsureLogDir
|
|
|
|
if ($Arg -eq "-f") {
|
|
if (-not (Test-Path $LogOut)) {
|
|
WriteLog "No log file yet. Start the server first." "Yellow"
|
|
return
|
|
}
|
|
WriteLog "Following $LogOut (Ctrl-C to stop)" "Cyan"
|
|
Get-Content $LogOut -Wait -Tail 30
|
|
} else {
|
|
$n = if ($Arg -match '^\d+$') { [int]$Arg } else { 40 }
|
|
WriteLog "--- stdout (last $n lines) ---" "Cyan"
|
|
if (Test-Path $LogOut) { Get-Content $LogOut -Tail $n } else { WriteLog " (empty)" "DarkGray" }
|
|
WriteLog ""
|
|
WriteLog "--- stderr (last 20 lines) ---" "Yellow"
|
|
if (Test-Path $LogErr) { Get-Content $LogErr -Tail 20 } else { WriteLog " (empty)" "DarkGray" }
|
|
}
|
|
}
|
|
|
|
# ── Dispatch ──────────────────────────────────────────────────────────────
|
|
|
|
switch ($Command) {
|
|
"start" { Start-Server }
|
|
"stop" { Stop-Server }
|
|
"restart" { Restart-Server }
|
|
"status" { Show-Status }
|
|
"logs" { Tail-Logs }
|
|
}
|
|
|
|
|
|
|
|
|
|
|