stop.sh•4.3 kB
#!/usr/bin/env bash
# Stop MCP Server (repo-level wrapper, prefers installed target if present)
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
# Default installation directory: enforce $HOME/MCPServer for user installs
INSTALL_DIR="${INSTALL_DIR:-$HOME/MCPServer}"
# If an installed runtime exists under INSTALL_TARGET use that first
if [ -n "${INSTALL_TARGET:-}" ] && [ -x "$INSTALL_TARGET/bin/stop.sh" ]; then
exec "$INSTALL_TARGET/bin/stop.sh" "$@"
fi
# Normalize .env line endings and source it when present
if [ -f "$INSTALL_DIR/.env" ]; then
if command -v dos2unix >/dev/null 2>&1; then
dos2unix "$INSTALL_DIR/.env" >/dev/null 2>&1 || true
else
if grep -q $'\r' "$INSTALL_DIR/.env" >/dev/null 2>&1; then
sed -i 's/\r$//' "$INSTALL_DIR/.env" || true
fi
fi
set -a
# shellcheck source=/dev/null
source "$INSTALL_DIR/.env" || true
set +a
fi
if [ ! -d "$INSTALL_DIR" ]; then
echo "Installation directory not found: $INSTALL_DIR" >&2
exit 0
fi
PIDFILE="${PIDFILE:-$INSTALL_DIR/run/mcp-server.pid}"
PGIDFILE="${PGIDFILE:-$INSTALL_DIR/run/mcp-server.pgid}"
if [ ! -f "$PIDFILE" ]; then
echo "PID file not found; server not running or already stopped." >&2
exit 0
fi
PID=$(cat "$PIDFILE" 2>/dev/null || echo "")
if [ -z "$PID" ]; then
echo "PID file empty or unreadable; removing and exiting." >&2
rm -f "$PIDFILE" || true
exit 0
fi
# Verify that PID points to our python/uvicorn/server.py process. If not,
# treat as stale (to avoid killing unrelated PIDs) and remove pidfile.
if [ -f "/proc/$PID/cmdline" ]; then
CMDLINE=$(tr '\0' ' ' < /proc/$PID/cmdline)
if ! echo "$CMDLINE" | grep -E "(python|uvicorn|server.py)" >/dev/null 2>&1; then
echo "PID $PID does not look like our server process: $CMDLINE" >&2
echo "Removing stale pidfile." >&2
rm -f "$PIDFILE" || true
rm -f "$PGIDFILE" || true
exit 0
fi
fi
# If we have a process group id, attempt to stop the whole group first
if [ -f "$PGIDFILE" ]; then
PGID=$(cat "$PGIDFILE" 2>/dev/null || echo "")
if [ -n "$PGID" ]; then
echo "Stopping process group $PGID..."
kill -TERM -"$PGID" 2>/dev/null || true
# wait up to 8 seconds for graceful shutdown
for i in {1..8}; do
if ! kill -0 -"$PGID" >/dev/null 2>&1; then
break
fi
sleep 1
done
if kill -0 -"$PGID" >/dev/null 2>&1; then
echo "Process group $PGID did not stop; force killing..."
kill -KILL -"$PGID" 2>/dev/null || true
fi
rm -f "$PGIDFILE" || true
fi
fi
# Fallback: stop main PID if still running
if kill -0 "$PID" >/dev/null 2>&1; then
echo "Stopping PID $PID..."
kill -TERM "$PID" 2>/dev/null || true
# wait up to 6 seconds for graceful shutdown
for i in {1..6}; do
if ! kill -0 "$PID" >/dev/null 2>&1; then
break
fi
sleep 1
done
if kill -0 "$PID" >/dev/null 2>&1; then
echo "PID $PID did not exit, force killing..."
kill -KILL "$PID" 2>/dev/null || true
fi
else
echo "Process $PID not running; cleaning up pidfile." >&2
fi
rm -f "$PIDFILE" || true
echo "Stopped"
# Extra fallback: detect stray listeners on common ports and kill python/uvicorn processes
for PORT in ${MCP_PORT:-3000} 8000; do
PIDS=""
if command -v lsof >/dev/null 2>&1; then
PIDS=$(lsof -t -iTCP:"$PORT" -sTCP:LISTEN 2>/dev/null || true)
else
if command -v ss >/dev/null 2>&1; then
PIDS=$(ss -ltnp 2>/dev/null | grep -F ":$PORT " | grep -oP 'pid=\K[0-9]+' || true)
fi
fi
for p in $PIDS; do
if [ -f "/proc/$p/cmdline" ]; then
CMD=$(tr '\0' ' ' < /proc/$p/cmdline)
if echo "$CMD" | grep -E "(python|uvicorn|server.py)" >/dev/null 2>&1; then
echo "Killing stray process $p listening on port $PORT: $CMD"
kill -TERM "$p" 2>/dev/null || true
sleep 1
if kill -0 "$p" >/dev/null 2>&1; then
kill -KILL "$p" 2>/dev/null || true
fi
fi
fi
done
done