init
This commit is contained in:
229
server.ps1
Normal file
229
server.ps1
Normal file
@@ -0,0 +1,229 @@
|
||||
<#
|
||||
.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 }
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user