pz-nitrado-mcp
# pz-nitrado-mcp
An [MCP](https://modelcontextprotocol.io) server for managing a **Project Zomboid**
dedicated server hosted on **Nitrado**, so you can check status, tune sandbox
settings and manage mods by talking to Claude instead of clicking through the
web panel.
There is no official Nitrado MCP server; the only community one targets ARK. This
one speaks PZ: it knows where `servertest.ini` and `SandboxVars.lua` actually live,
edits the `.ini` without trashing your comments, and keeps `Mods` and
`WorkshopItems` in sync.
## Tools
| Tool | What it does |
|---|---|
| `nitrado_list_services` | List every service on the account — use it to find your `service_id` |
| `nitrado_event_log` | Audit trail: who restarted, who changed which file, and when |
| `pz_list_config_profiles` | Saved config snapshots — the server's config history |
| `pz_get_config_profile` | A config file as it was when a snapshot was taken |
| `pz_diff_config_profile` | What changed between a snapshot and the live config |
| `pz_server_status` | Online/offline, address, slots, players connected, version |
| `pz_locate_configs` | Discover the real config paths (the server name is host-assigned) |
| `pz_list_files` | List or recursively glob a remote directory |
| `pz_read_file` | Read any file; `ini` / `sandbox` / `spawnregions` are shortcuts |
| `pz_get_settings` | The main `.ini` parsed into key/value pairs |
| `pz_get_sandbox` | Sandbox settings, searchable, with the docs for each enum |
| `pz_set_sandbox` | Change sandbox settings in place, comments preserved |
| `pz_get_mods` | Current `Mods` and `WorkshopItems`, in load order |
| `pz_list_logs` | Available logs newest-first: console plus PZ's rotated logs |
| `pz_tail_log` | Tail the last N lines of a log, with an optional filter |
| `pz_write_file` | Overwrite a file (keeps a timestamped `.bak`) |
| `pz_set_settings` | Set individual `.ini` keys, preserving comments and ordering |
| `pz_set_mods` | Add / remove / replace mods, updating both id lists together |
| `pz_restart` | Restart the server so changes take effect — also starts a stopped one |
| `pz_stop` | Stop the server |
Every write takes a timestamped backup next to the original first, so a bad edit
is always recoverable from the file server. The five most recent backups per
file are kept and older ones are pruned — set `PZ_BACKUP_KEEP` to change that,
or to `0` to disable backups entirely.
## Setup
### 1. Get a Nitrado API token
In the [Nitrado panel](https://server.nitrado.net): your account menu →
**Developer** (or `/developer`) → **Create token**. Tick the **`service`** scope
and nothing else — it covers gameserver status, the file server and restarts.
In particular do **not** tick `service_order`, which allows ordering (and paying
for) new services. The token is shown once; treat it like a password.
### 2. Install
```bash
git clone https://github.com/aolmosj/pz-nitrado-mcp.git
cd pz-nitrado-mcp
python3 -m venv .venv
.venv/bin/pip install -r requirements.txt # editable install; add ".[dev]" for tests
```
Or straight from GitHub into an existing environment:
```bash
pip install git+https://github.com/aolmosj/pz-nitrado-mcp.git
```
### 3. Store the token
Put it in the shared config directory, so every project and session picks it up
and it never lives inside an editor config file:
```bash
mkdir -p ~/.config/nitrado && chmod 700 ~/.config/nitrado
printf '%s' 'your-token-here' > ~/.config/nitrado/token
chmod 600 ~/.config/nitrado/token
# optional: a default service id, so tools can omit it
printf '%s' '1234567' > ~/.config/nitrado/service_id
```
Use `printf` rather than `echo` to avoid a trailing newline — though the server
strips whitespace anyway. It warns on stderr if the token file is readable by
other users.
Don't know your service id? Leave it out and ask `nitrado_list_services`.
### 4. Register it with Claude Code
Add to `.mcp.json` in your project (or `~/.claude.json` for all projects). No
credentials here — they come from the config directory:
```json
{
"mcpServers": {
"pz-nitrado": {
"command": "/absolute/path/to/pz-nitrado-mcp/.venv/bin/python",
"args": ["-m", "pz_nitrado_mcp"]
}
}
}
```
The installed console script works too, if you prefer a single token:
`"command": "/absolute/path/to/.venv/bin/pz-nitrado-mcp"` with no `args`.
> **Don't reach for a `cwd` field.** It is tempting to skip the install and
> point `cwd` at the checkout instead — but Claude Code does not apply it, so
> the server starts somewhere else, fails with `No module named
> pz_nitrado_mcp`, and the client reports only
> `-32000: Connection closed`. `claude mcp list` shows the underlying error.
### Configuration reference
Each setting is resolved from the first source that has it:
| Setting | 1. Env var | 2. `<VAR>_FILE` | 3. Config dir |
|---|---|---|---|
| Token (required) | `NITRADO_TOKEN` | `NITRADO_TOKEN_FILE` | `~/.config/nitrado/token` |
| Default service id | `NITRADO_SERVICE_ID` | `NITRADO_SERVICE_ID_FILE` | `~/.config/nitrado/service_id` |
`XDG_CONFIG_HOME` is honoured if set.
Two behaviour switches, environment-only:
| Variable | Default | Effect |
|---|---|---|
| `PZ_ALLOW_WRITES` | `1` | `0` runs read-only — no writes, no restarts. A reasonable default for a shared or automated setup. |
| `PZ_BACKUP_KEEP` | `5` | Backups retained per file; older ones are pruned after each write. `0` disables backups entirely. |
## Usage
Once registered, just ask:
- *"¿Está arriba el servidor? ¿Quién está jugando?"*
- *"Sube la población de zombis a insane y baja el loot a rare"*
- *"Añade Authentic Z y Brita's Weapon Pack, y reinicia"*
- *"¿Qué mods tengo instalados ahora mismo?"*
### Reading logs
PZ rotates its logs on every server start, naming them
`DD-MM-YY_HH-MM-SS_<kind>.txt` under `Zomboid/Logs/`, alongside a live
`server-console.txt`. You don't need to know the timestamps — `pz_tail_log`
takes a *kind* and resolves it to the most recent file:
- `console` — the live server console (default; where crashes and mod load
failures show up)
- `chat`, `user`, `admin`, `pvp`, `map`, `item`, `cmd` — PZ's per-category logs
- `DebugLog-server` — verbose server debug output
Only the tail is fetched, using Nitrado's `seek` endpoint with a negative byte
offset, so tailing a multi-megabyte log stays cheap. The endpoint caps each
request at 64 KiB, so large tails page backwards in chunks. The optional
`contains` filter is applied to the fetched window — if you expect older
matches, raise `lines` rather than assuming there are none.
### Sandbox settings
`SandboxVars.lua` holds ~270 settings — zombie population and speed, loot
rarity, day length, XP rates, erosion. Most are numeric enums whose meaning is
documented in comments right above them, and `pz_get_sandbox` returns those
comments alongside the value:
```
pz_get_sandbox(search="insane")
Zombies = 4
Changing this also sets the "Population Multiplier". Default = Normal
1 = Insane 2 = Very High 3 = High 4 = Normal 5 = Low 6 = None
```
So "make the zombies insane" becomes `pz_set_sandbox({"Zombies": 1})` without
anyone having to memorise the numbering. Nested settings use dotted names
(`ZombieLore.Speed`, `MultiplierConfig.*`).
Edits are line-surgical: only the lines you name change, and the file's 738
comments, ordering and numeric formatting survive untouched. Writing `2` into a
setting that reads `0.8` produces `2.0`, not `2`.
Unknown setting names are **rejected**, not appended. The game silently ignores
settings it doesn't recognise, so a typo would otherwise look like it worked
and do nothing; the error suggests the closest real names instead.
### Configuration history
Nitrado snapshots the config most days, and keeps whatever you save by hand —
the panel's "Perfiles de Configuración". `pz_diff_config_profile` is the useful
end of it: point it at a snapshot and it reports only what differs from the
config running now.
```
pz_diff_config_profile("09.07.2026 - 22:21")
differences: 111
PVP: 'false' -> 'true'
NoFire: 'true' -> 'false'
Mods: 'Ashenwood;BBL;Blackwood;…(+6000 more chars)' -> 'CleanHotBar'
```
> ⚠️ **These snapshots contain passwords in plain text** — the admin password
> in the settings block, and the server and RCON passwords inside the config
> file. Every profile tool redacts them by default; `include_secrets=true`
> turns that off deliberately. A profile is a full copy of your config, so
> treat its output as sensitive.
Long values are abbreviated so one enormous `Mods` line doesn't bury the rest
of a diff; pass `full_values=true` when you need the whole thing.
### Event log vs game log
Two different things, easy to confuse:
- **`nitrado_event_log`** — what *people* did to the service. Restarts, stops,
file uploads and deletions, settings changes, game installs, each with the
account name and a timestamp. This is the panel's "Registro de eventos".
- **`pz_tail_log`** — what the *game* did. Startup sequence, errors, chat,
connections.
"Who restarted the server at 3am?" is the first; "why did it crash?" is the
second.
Config writes made through this MCP server show up in the event log as
`filebrowser` uploads, so its own activity is auditable alongside everything
else. Filtering is done client-side because the endpoint ignores filter
parameters, and category matching is case-insensitive — the live log contains
both `server` and `Server`, and an exact match silently drops the latter (which
is where the `error` entries live).
### A note on mods
PZ needs **two** identifiers per mod and they are not interchangeable:
- `workshopId` — the numeric Steam Workshop id, what the server downloads
- `modId` — the string from the mod's `mod.info`, what the game loads
`pz_set_mods` takes both and keeps `Mods=` and `WorkshopItems=` consistent. List
order is load order, which matters when mods conflict.
## Notes and limitations
- **Paths are discovered, not assumed.** On Nitrado the gameserver root holds
the *game install*; the server's own state lives under `profile/Zomboid`
(`Server/`, `Logs/`, `Saves/`, `server-console.txt`). Other hosts differ, so
the server probes candidate locations and identifies the right one by its
contents. `pz_locate_configs` and `pz_list_logs` report what it found.
- **Config files only exist after first boot.** PZ writes `servertest.ini` and
friends when the server first starts, so `pz_locate_configs` will fail on a
server that has never run. Start it once from the panel.
- **Changes need a restart.** Nothing is applied live; every write tool says so.
- **`pz_restart` is also "start".** The API has no start action, so a server
stopped with `pz_stop` comes back only via `pz_restart`. A restart takes about
a minute (`started → restarting → stopping → started`), and the status lags a
few seconds behind the request — a status of `started` immediately after
calling restart doesn't mean it was ignored.
- Build 42 wants at least 8 GB of RAM. If the plan is smaller, you will feel it
once several players are on.
## Development
```bash
.venv/bin/pip install -e ".[dev]" # pytest + pytest-asyncio
.venv/bin/python -m pytest tests/ -q
```
The tests cover the config-manipulation logic — `.ini` editing, mod list
merging, path resolution — which is the part that can corrupt a live server, and
they run without touching the network.
## License
MIT
TDQS
Scored across 20 tools
Every tool has a clearly distinct purpose: service listing, live status, config file discovery, file operations, settings parsing, mod management, log tailing, config profile snapshots, sandbox editing, and lifecycle control. Overlaps like pz_read_file vs pz_get_settings are explicitly disambiguated in descriptions.
Most tools follow a consistent verb_noun pattern with a pz_ prefix, but two account-level tools use a nitrado_ prefix, creating a minor deviation. The prefix distinction is intentional and logical, but the mixed prefixes prevent a perfect 5.
At 20 tools, the server is slightly above the typical 3-15 well-scoped range, but each tool serves a distinct purpose in managing both the Nitrado account and the Project Zomboid server. The breadth is justified by the dual scope (account-level and server-level), so it's only slightly heavy.
The tool surface covers the full lifecycle of a PZ server: discovery, status, configuration (raw, parsed, and sandbox), mods, logs, config history, and restart/stop. Missing features like a separate start tool are unnecessary because restart serves that role, and the ability to read/write any file fills gaps.