Tools
22 tools, in three groups. Tools marked read-only are the ones that survive HETZNER_READ_ONLY=true; tools marked 👤 ask a person before they act, through MCP elicitation — a dialog the model cannot answer on its behalf. Where the client cannot show one they fall back to a two-call confirm_token, and ELICITATION=false takes that fallback deliberately. See Asking a person.
Every tool declares an outputSchema and answers with structuredContent beside the text block, so a client can use a result without parsing prose. All of them carry untrusted: true and source: "hetzner-cloud-api" as fields of that object — there is no exception list, because record values, comments, labels and zone files are written by whoever controls the zone. The <untrusted-data> fence stays in the text block, where it is the readable presentation of that marker.
Every tool declares all four MCP annotations — readOnlyHint, destructiveHint, idempotentHint, openWorldHint. openWorldHint is false throughout: this server talks to the one Hetzner Cloud API it is configured for.
All 22 are registered unless you say otherwise. HETZNER_ALLOW_TOOLS and HETZNER_DENY_TOOLS narrow the list to the ones you want, and essential selects a curated eight — see choosing the tools that load.
Shared argument types:
| Argument | Type |
|---|---|
zone | Zone ID or name, [A-Za-z0-9._-]+, e.g. example.com |
name | RRSet name relative to the zone, [A-Za-z0-9@*._-]+, e.g. www or @ for the apex |
type | A, AAAA, CAA, CNAME, DS, HINFO, HTTPS, MX, NS, PTR, RP, SOA, SRV, SVCB, TLSA, TXT |
records | Array of { value, comment? }, at least one; value is zone-file syntax |
ttl | Integer seconds, 60 – 2147483647 |
labels | Object of string key/value pairs |
page, per_page | Pagination; per_page 1 – 100, default 25 |
Zones
list_zones read-only
essential
List the zones of the project with status, mode, default TTL, assigned nameservers and record counts.
Arguments: name? (exact match), mode? (primary | secondary), label_selector? (e.g. env=prod), page?, per_page?
get_zone read-only
essential
Full details of a single zone.
Arguments: zone
create_zone 👤 asks a person when it carries content
Create a zone. primary for zones managed at Hetzner, secondary with primary_nameservers to transfer from external primaries. A primary zone can be initialized from a zone file in the same call.
Arguments: name, mode, ttl?, labels?, primary_nameservers?, zonefile?, confirm_token?
Creating an empty zone is additive and asks nobody. Creating one with primary_nameservers or a zonefile asks first: those two arguments carry the whole content of the zone, and if the name is already delegated to Hetzner's nameservers, that content is what the internet is served. They are the same payloads change_primary_nameservers and import_zonefile are guarded for, so denying either of those tools without this guard would not have taken the capability away.
WARNING
A tsig_key inside primary_nameservers becomes part of the conversation context and the client transcript. Treat it as disclosed and rotate if in doubt.
update_zone
Replace the labels of a zone. The given set replaces all existing labels; other properties have their own change_zone_* tools.
Arguments: zone, labels
delete_zone 👤 asks a person
Permanently delete a zone and every record in it. The refusal reports how many records the zone holds.
Arguments: zone, confirm_token?
export_zonefile read-only
essential
Export the zone as a BIND zone file. Worth calling before any bulk change — it is the only backup you get.
Arguments: zone
import_zonefile 👤 asks a person
Import a BIND zone file into an existing primary zone, replacing its current records. The approval is bound to a hash of the zone file, so a confirmation for one import cannot execute a different one. Returns an action to poll.
Arguments: zone, zonefile, confirm_token?
change_zone_ttl
Change the zone's default TTL, which applies to RRSets without an explicit one.
Arguments: zone, ttl
change_zone_protection 👤 asks a person before it disables
Enable or disable delete protection. Enabling is immediate; disabling removes the last safeguard in front of delete_zone and is gated like a deletion.
Arguments: zone, delete (boolean), confirm_token?
change_primary_nameservers 👤 asks a person
Replace the primary nameservers of a secondary zone. The entire zone content is taken from the new primaries on the next transfer. The token is bound to the nameserver list.
Arguments: zone, primary_nameservers, confirm_token?
Each entry: { address, port?, tsig_key?, tsig_algorithm? } with tsig_algorithm one of hmac-md5, hmac-sha1, hmac-sha256.
RRSets (record sets)
An RRSet is all records of one name and type — for example every A record for www. Hetzner's API works in RRSets, not individual records, which is why replacing one value means passing the whole set.
list_rrsets read-only
essential
List the RRSets of a zone with records, TTLs and protection status.
Arguments: zone, name?, type? (array, e.g. ["A", "AAAA"]), label_selector?, page?, per_page?
get_rrset read-only
essential
A single RRSet by name and type.
Arguments: zone, name, type
create_rrset 👤 asks a person for authority records
essential
Create a new RRSet. Fails if one with the same name and type exists — use set_records or add_records then. Gated only when the record decides who answers for a name — see Asking a person.
Arguments: zone, name, type, records, ttl?, labels?, confirm_token?
The confirmation binds ttl and labels as well as the record list: this call writes all three, so a token issued for one TTL cannot redeem another.
update_rrset
Replace the labels of an RRSet. Records and TTL have their own tools.
Arguments: zone, name, type, labels
delete_rrset 👤 asks a person
essential
Permanently delete an RRSet with all its records.
Arguments: zone, name, type, confirm_token?
set_records 👤 asks a person
essential
Replace all records of an RRSet. Anything not listed is removed. The token is bound to a hash of the record list.
Arguments: zone, name, type, records, confirm_token?
add_records 👤 asks a person for authority records
Append records to an RRSet, keeping the existing ones. Creates the RRSet if it does not exist — so denying create_rrset does not remove the ability to create one. Gated only when the record decides who answers for a name — see Asking a person.
Arguments: zone, name, type, records, ttl?, confirm_token?
The confirmation binds ttl as well as the record list: this call writes both, and a week-long TTL on a record somebody else added is how long the correction takes to reach the caches.
remove_records 👤 asks a person
Remove specific records, matched by value. Removing the last one deletes the RRSet. The token is bound to a hash of the record list.
Arguments: zone, name, type, records, confirm_token?
change_rrset_ttl
Change an RRSet's TTL, or pass null to fall back to the zone default.
Arguments: zone, name, type, ttl (nullable)
change_rrset_protection 👤 asks a person before it disables
Enable or disable change protection. A protected RRSet cannot be changed or deleted. Disabling is gated.
Arguments: zone, name, type, change (boolean), confirm_token?
Actions
Slow operations — zone file imports above all — return an action instead of a result. These two tools follow them.
list_zone_actions read-only
List actions of all zones, or of one zone if given.
Arguments: zone?, status? (array of running | success | error), page?, per_page?
get_zone_action read-only
A single action by ID, to check its status and result.
Arguments: action_id (positive integer)
Annotations
Every tool carries MCP annotations so a host can apply a permission policy without hard-coding names:
| Annotation | Tools |
|---|---|
readOnlyHint | the seven read tools |
destructiveHint | delete_zone, delete_rrset, import_zonefile, set_records, remove_records, change_primary_nameservers |
idempotentHint | update_zone, update_rrset, change_zone_ttl, change_rrset_ttl, both change_*_protection |