Skip to content

Configuration

Everything is configured through environment variables. There is no config file, and nothing is read from disk.

VariableRequiredDefaultDescription
HETZNER_API_TOKENyesHetzner Cloud API token, project-scoped
HETZNER_READ_ONLYnofalsetrue registers only the read tools
HETZNER_API_BASE_URLnohttps://api.hetzner.cloud/v1Override the API base URL

The full descriptions are in the environment reference.

HETZNER_API_TOKEN

A Hetzner Cloud API token from Security → API tokens in the project that holds your zones. It is sent as a Bearer header on every request.

Pass it through your MCP client's env block. Not on a command line, where ps shows it to every user on the machine, and not in a file you might commit.

The server reads it once at startup and then deletes it from process.env, so a later crash report, a Node diagnostic report or a dependency that dumps the environment finds nothing there.

Read-only mode

bash
HETZNER_READ_ONLY=true

Accepted as true, 1 or yes, case-insensitively.

This does not reject write calls — it never registers the write tools in the first place. tools/list returns seven tools, and a client asking for delete_zone gets "tool not found" from the protocol layer. There is no code path from a write request to the API.

Pair it with a read-only Hetzner token for defence in depth: the token stops the API from accepting a write, and read-only mode stops the model from ever seeing a tool that could attempt one.

json
{
  "mcpServers": {
    "hetzner-dns": {
      "command": "npx",
      "args": ["-y", "hetzner-dns-mcp"],
      "env": {
        "HETZNER_API_TOKEN": "your-read-only-token",
        "HETZNER_READ_ONLY": "true"
      }
    }
  }
}

Two servers, two roles

Nothing stops you registering the same package twice — once read-only for everyday questions, once with write access under a different name for the sessions where you actually intend to change something.

HETZNER_API_BASE_URL

Only useful for testing against a local mock. It is validated before anything is sent:

  • must parse as a URL, and be httpshttp is accepted only for localhost, 127.0.0.1 and [::1]
  • must not contain credentials (https://user:pass@… is rejected)
  • a non-default host produces a warning on stderr, because your token is about to be sent there

A value that fails these checks exits the process rather than starting a server that would leak the token on its first call. The value itself is never printed back, since a malformed one can carry a user:token@ part.

Timeouts and limits

These are not configurable, and are listed so you know what to expect:

BehaviourValue
Request timeout30 seconds
Redirectsrefused (redirect: 'error')
Confirmation token TTL5 minutes, single use
Pending tokens kept100, oldest evicted
Single value in a resulttruncated at 4 000 characters
Whole resulttruncated at 200 000 characters
Upstream error bodytruncated at 2 000 characters; HTML dropped

If a list tool truncates, narrow it with per_page and page rather than retrying — the caps exist because a large zone would otherwise fill the context window in a single call.

Released under the MIT License.