Skip to main content
Glama
aolmosj

pz-nitrado-mcp

by aolmosj
README.md
# 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

A4.3/5.0

Scored across 20 tools

Disambiguation5/5

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.

Naming Consistency4/5

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.

Tool Count4/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.

Completeness5/5

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.

Maintenance

ActivityMaintained
ResponsivenessSyncing