Process Lifecycle Management¶
Operators can reconfigure a running server — locally or inside a Docker container — without recreating the container. Four CLI subcommands cover the full lifecycle:
Command |
Signal |
What it does |
|---|---|---|
|
|
Soft reload. Re-reads |
|
|
Hard restart. Re-reads |
|
(read-only) |
Print PID, uptime, transport, port, and |
|
|
Clean shutdown. Same signal Docker uses. No respawn. |
Lifecycle subcommands are mutually exclusive with the serve path — running zscaler-mcp with no subcommand starts the server as before.
When to reload vs restart¶
Reload (SIGHUP) when you only flipped a runtime toggle:
ZSCALER_MCP_LOG_TOOL_CALLS, log level, or a non-credential env var. The Zscaler SDK client has no module-level cache — it’s created on every tool call — so credential rotations land naturally on the next call. The auth middleware token cache is keyed by credential hash, so old entries miss the cache and re-validate against new values. MCP sessions survive.Restart (SIGUSR2) when you changed something read once at startup: rotated credentials, changed
--toolsetsselection, flipped--enable-write-tools, swapped vanity domain, or anything that resolves the entitlement filter. Sessions die — clients reconnect.
The state of the server (sessions, sockets, in-flight requests) is preserved across reload and discarded across restart. Choose by what you changed, not by what’s convenient.
The PID file¶
When the server starts (after parse_args() succeeds, before server.run()), it writes a JSON PID file containing the running PID, start time, transport, host:port, the resolved .env path, the original argv, and the Python interpreter path. The PID file is the source of truth for status, reload, restart, and stop.
Location priority:
--pid-file <path>/ZSCALER_MCP_PID_FILE/var/run/zscaler-mcp.pid(typical inside containers with write access)/tmp/zscaler-mcp.pid~/.zscaler-mcp/server.pid(auto-created)
For multiple instances on the same host (e.g. one per port), set ZSCALER_MCP_PID_FILE=/tmp/zscaler-mcp-<port>.pid per instance.
Env-source classification¶
The status / reload / restart commands need to be honest about whether re-reading .env will actually change anything. The CLI classifies the env source on every invocation:
Label |
What it means |
|---|---|
|
|
|
Same as above but the path is |
|
The originally-recorded |
|
The originally-recorded |
|
No |
The classifier surfaces this in the status output so operators don’t have to guess.
The docker cp workflow¶
For a container already running without a bind mount, you can drop a .env in at any time:
docker cp ./.env <container>:/app/.env
docker exec <container> zscaler-mcp restart
The classifier reports fresh-discovery and restart execs a fresh process that picks up the newly-placed file. No container recreate needed.
Bind-mount workflow (recommended)¶
For long-lived deployments, bind-mount the .env once at container start and you can edit it on the host whenever you want:
docker run -d --name zscaler-mcp-server \
--env-file /path/to/.env \ # boot-time injection
-v /path/to/.env:/app/.env:ro \ # live re-read on restart
-e ZSCALER_MCP_DOTENV_PATH=/app/.env \
zscaler/zscaler-mcp-server:latest --transport streamable-http
# Edit the file on the host
$EDITOR /path/to/.env
# Apply changes
docker exec zscaler-mcp-server zscaler-mcp restart
Why --env-file alone doesn’t work for live edits¶
docker run --env-file=./.env … reads the host file once at docker run time and copies the values into the container’s Config.Env metadata. After that moment, the container has no link to the host file — you could delete it on the host and the container still holds the values. docker stop && docker start <ctr> reuses Config.Env from metadata; it does not re-read the host file.
The only ways to change a running container’s environment are:
docker rm+docker run(full recreate)Bind-mount the file inside so PID 1 can re-read it on reload/restart
docker cpthe file in, thenzscaler-mcp restart
The lifecycle subcommands work with (2) and (3). (1) is always available but disrupts sessions.
Cross-platform notes¶
Linux / macOS: full support.
Windows: SIGHUP and SIGUSR2 don’t exist.
reload/restartprint a clear error and exit2.statusreads the PID file.stopfalls back to SIGTERM. Native Windows operators restart their supervisor (Docker Desktop, NSSM, etc.) directly. Container deployments are unaffected — the container OS is Linux regardless of host.
Signal summary¶
Signal |
Handler |
Behaviour |
|---|---|---|
|
Custom |
Soft reload. Re-reads |
|
Custom |
Hard restart via |
|
Default (uvicorn) |
Clean shutdown. Used by |
|
Default (uvicorn) |
Same as SIGTERM. Used by Ctrl+C. |
Environment summary¶
Setting |
Effect |
|---|---|
|
Override the PID file location. Set per-instance when running multiple servers on the same host. |
|
Explicit path to the |
See also¶
Tool-Call Audit Logging — captures every tool call that lands during the session.
MCP Client Authentication — the auth middleware cache survives soft reloads and is re-keyed by credential hash.
Troubleshooting Guide — diagnosing reload / restart failures.