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 needconfirm_nameequal to the server name. - Protected resources. A server is protected when it is in
PROTECTED_SERVERS, has the Hetzner labelprotected=true, is markedprotectedin the host registry, or has Hetzner delete/rebuild protection on. Destructive calls on it are refused even withconfirm: true. - Host registry. SSH only reaches hosts listed in
config/hosts.json. File reads and writes stay inside each host'sallowed_paths..envfiles return their keys, never values. - Audit log. Every call (read, dry run, refused, executed) is appended to
data/audit.jsonlwith 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:
{
"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.
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.
License
MIT