hetzner-mcp
by monk24215
README.md
# hetzner-mcp
An MCP server that lets Claude operate a Hetzner estate: Cloud servers, DNS (Cloudflare and Hetzner DNS), hosts over SSH, Coolify, and multi-step recipes. Every write is a dry run until confirmed, protected servers refuse destructive calls, and every call is audited.
Status: v0.1.0. Built and tested against mocked APIs, a real SSH host, real Docker and a real Caddy. Not yet pointed at a live Hetzner project.
## Modules and tools (53 with everything configured)
| Module | Switched on by | Tools |
|---|---|---|
| fleet | always | `hz_fleet_overview`, `hz_host_list`, `hz_host_register`, `hz_host_remove` |
| cloud | `HCLOUD_TOKEN` | `hz_server_list`, `hz_server_get`, `hz_catalog`, `hz_action_status`, `hz_resource_list`, `hz_server_create`, `hz_server_power`, `hz_server_rebuild`, `hz_server_delete`, `hz_server_resize`, `hz_snapshot_create`, `hz_snapshot_delete`, `hz_backup_set`, `hz_firewall_create`, `hz_firewall_rules_set`, `hz_firewall_attach`, `hz_volume_create`, `hz_volume_action`, `hz_network_create`, `hz_network_attach`, `hz_ip_set_ptr`, `hz_floating_ip_assign`, `hz_ssh_key_add` |
| dns | `CLOUDFLARE_API_TOKEN` and/or `HCLOUD_TOKEN` | `dns_zone_list`, `dns_records_list`, `dns_record_set`, `dns_record_delete`, `dns_check` |
| host | `SSH_KEY` or `SSH_KEY_PATH` | `host_status`, `host_docker_ps`, `host_logs`, `host_file_read`, `host_caddy_routes`, `host_file_write`, `host_compose_action`, `host_caddy_route`, `host_run`, `host_bootstrap` |
| coolify | `COOLIFY_BASE_URL` + `COOLIFY_API_TOKEN` | `coolify_list`, `coolify_get`, `coolify_deployments`, `coolify_env_list`, `coolify_deploy`, `coolify_app_action`, `coolify_env_set` |
| recipes | SSH key (+ `HCLOUD_TOKEN` for new servers) | `recipe_new_app_server`, `recipe_deploy_stack` |
| audit | always | `audit_log` |
`hz_capabilities` is always present and says which modules are on and what each one needs.
## Safety model
- **Dry run by default.** Write tools return a plan unless called with `confirm: true`. Delete and rebuild also need `confirm_name` equal to the server name.
- **Protected resources.** A server is protected when it is in `PROTECTED_SERVERS`, has the Hetzner label `protected=true`, is marked `protected` in the host registry, or has Hetzner delete/rebuild protection on. Destructive calls on it are refused even with `confirm: true`.
- **Host registry.** SSH only reaches hosts listed in `config/hosts.json`. File reads and writes stay inside each host's `allowed_paths`. `.env` files return their keys, never values.
- **Audit log.** Every call (read, dry run, refused, executed) is appended to `data/audit.jsonl` with secrets masked.
- **Caddy edits** are fenced with markers, backed up, validated and rolled back on failure before a reload.
## Stack
Node 22, TypeScript (strict), `@modelcontextprotocol/sdk`, zod, ssh2, Express (Streamable HTTP), Biome, Vitest, pnpm, Docker.
## Quickstart (local, stdio)
```
pnpm install
cp .env.example .env # fill in what you have
cp config/hosts.example.json config/hosts.json
pnpm build
```
Claude Desktop / Claude Code config:
```json
{
"mcpServers": {
"hetzner": {
"command": "node",
"args": ["C:\\path\\to\\hetzner-mcp\\dist\\index.js"],
"env": { "HCLOUD_TOKEN": "…", "SSH_KEY_PATH": "C:\\Users\\you\\.ssh\\hetzner_mcp_ed25519", "HOSTS_FILE": "C:\\path\\to\\hetzner-mcp\\config\\hosts.json" }
}
}
}
```
Hosted (HTTP, behind Caddy on the app server): see [docs/RUNBOOK.md](docs/RUNBOOK.md).
## CI checks
```
pnpm typecheck && pnpm lint && pnpm test
```
CI runs the same three plus `pnpm build` and a gitleaks secret scan.
## Growing it
Add a folder under `src/modules/<id>/` exporting a `Module` (`status()` + `register()`), register tools through the `ToolKit` (`t.read` / `t.write`), and list it in `src/modules/index.ts`. The guard, audit and capability report come for free. See [docs/CONTRIBUTING.md](docs/CONTRIBUTING.md).
## License
MIT
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues