> ## Documentation Index
> Fetch the complete documentation index at: https://docs.dnzwithazee.com/llms.txt
> Use this file to discover all available pages before exploring further.

# AI assistants (MCP)

> Connect Claude, Cursor, VS Code and other MCP clients to DNZ: sign in with OAuth at /mcp, or run dnz mcp with an API token.

DNZ is an [MCP](https://modelcontextprotocol.io) server: Claude, Cursor, VS Code and other
assistants that speak the Model Context Protocol can look up zones and records, explain why a name
answers the way it does, read the query log, statistics, threats and the audit log and, if you allow
it, change records. Ask things like "why doesn't app.example.com resolve from 10.0.4.7?", "audit
example.com" or "point staging at 192.0.2.40".

It is off until a superadmin [turns it on](#turn-it-on). Then there are two ways to connect:

* **A URL, `https://<your DNZ>/mcp`** (Streamable HTTP). The assistant signs in with OAuth on its first
  connection: you log in to DNZ as usual (two-factor included), see which app asks, and pick **read
  only** or **read and change**. Or it sends an API token as the bearer token.
* **A command, `dnz mcp`** (stdio), for assistants that start a program. It uses the session from
  `dnz login` or an API token, like every other `dnz` command, and is read only unless started with
  `--allow-writes`.

Either way every tool goes through DNZ's HTTP API as you (or as the token): you see what your roles
let you see, change approval applies, and history and the audit log show the change as made through
MCP, e.g. `alice via Claude`.

## Turn it on

The `/mcp` endpoint is **off** until a superadmin turns it on: nothing answers there and no app can
sign in. Turn it on under **Settings → Integrations → AI assistants → Turn on**, or:

```bash theme={null}
dnz settings mcp on
```

It takes effect at once, on every server of a cluster, without a restart. `dnz settings mcp off` (or
**Turn off**) shuts it again, and also shuts out the apps already signed in until it's back on; their
grants stay, so they work again when it is. To have it on from the first start, set `DNZ_MCP=true` (or
`mcp = true` under `[http]` in the config file); a switch made in the dashboard or the CLI wins over that.

`dnz mcp` with an API token works either way: it is a client of the API like any other `dnz` command.

## Connect

> [!TIP]
>
> **Settings → Integrations → AI assistants** in the dashboard has the URL for your server and these steps with copy buttons.

### Claude (claude.ai, Claude Desktop)

1. **Settings → Connectors → Add custom connector**.
2. Name it `DNZ` and paste `https://dns.example.com/mcp`.
3. **Connect**, log in to DNZ if asked, choose the access and **Allow**.

claude.ai connects from Anthropic's servers, so the URL must be reachable from the internet over
HTTPS. For a DNZ that is only on your network, use Claude Desktop with the `dnz mcp` command below.

### Claude Code

```bash theme={null}
claude mcp add --transport http dnz https://dns.example.com/mcp
```

Then run `/mcp` in Claude Code and choose **dnz → Authenticate**.

### Cursor

`~/.cursor/mcp.json` (or `.cursor/mcp.json` in a project):

```json theme={null}
{
  "mcpServers": {
    "dnz": { "url": "https://dns.example.com/mcp" }
  }
}
```

### VS Code

`.vscode/mcp.json`, or **MCP: Add Server… → HTTP**:

```json theme={null}
{
  "servers": {
    "dnz": { "type": "http", "url": "https://dns.example.com/mcp" }
  }
}
```

### With an API token instead of signing in

Any client that lets you set headers can skip OAuth: create a token under **Settings → API tokens** ([API tokens](https://docs.dnzwithazee.com/api/overview.md))
and send `Authorization: Bearer dnz_…`. The token's permissions and zones decide what the tools may
read and change. With Claude Code:

```bash theme={null}
claude mcp add --transport http dnz https://dns.example.com/mcp --header "Authorization: Bearer dnz_…"
```

### As a command (stdio)

Install the `dnz` binary where the assistant runs (it doesn't have to be the DNZ server), then
either sign in once with `dnz login --server https://dns.example.com`, or give it an API token:

```json theme={null}
{
  "mcpServers": {
    "dnz": {
      "command": "dnz",
      "args": ["mcp", "--server", "https://dns.example.com", "--allow-writes"],
      "env": { "DNZ_TOKEN": "dnz_…" }
    }
  }
}
```

Leave out `--allow-writes` for read only. That's `claude_desktop_config.json` for Claude Desktop, and
the same shape for Cursor; for Claude Code:

```bash theme={null}
claude mcp add dnz -e DNZ_TOKEN=dnz_… -- dnz mcp --server https://dns.example.com
```

This is also how to connect a DNZ that runs without the web UI (`DNZ_UI=false`), where signing in
with OAuth isn't possible.

## Tools

Tools that list something long take `page` (from 1) and `limit` (rows per page, at most 200), and
answer with `total`, `page` and `per_page` next to the list, so the assistant can tell there is more
and ask for the next page.

| Tool | Does |
| - | - |
| `whoami` | Who the connection acts as, its divisions, superadmin or not |
| `list_zones` | Zones you can see, optionally of one division or matching `search`; a page of each division at a time (`page`, `limit`, 50), with each division's total |
| `get_zone` | One zone's settings |
| `list_records` | A zone's records, by name, type or a search in name, value and comment (a name may be given in full, `www.example.com`: that record comes first); a page of names at a time (`page`, `limit` names, 50), each with all its records: `total` counts names, `matching` records |
| `export_zone` | A zone as a zone file |
| `zone_history` | A zone's versions: who changed what, when, from where; `page` and `limit` (20 a page) |
| `explain_query` | The decision path and answer for a name, type and client ([Query explain](https://docs.dnzwithazee.com/dns/query-explain.md)) |
| `query_log` | Recent queries, filtered by name, client or source; the last 24 hours unless `range` (`1h`, `24h`, `7d`, `all`) or `since`/`until`. In a cluster every server's, each row naming its server; `node` (a node id) for one server ([Query logs](https://docs.dnzwithazee.com/dns/query-logs.md#in-a-cluster)); `page` (from 1, 5,000 rows deep; `limit` is then the page size, 20 by default, at most 100) answers `more` and the `until` to pass for the next pages ([pages](https://docs.dnzwithazee.com/dns/query-logs.md#pages)) |
| `capture_queries` | Watch live queries matching keywords (typed, or saved keyword lists) for a few seconds, grouped by domain; `on` watches a DNZ secondary from its primary ([Live capture](https://docs.dnzwithazee.com/dns/live-capture.md)) |
| `capture_keyword_lists` | The saved keyword lists live capture can pick, server-wide and a division's |
| `traffic_stats` | Query counts, NXDOMAIN ratio, top names and clients |
| `dns_analytics` | One server's queries over a span (`range`: `1h` to `30d`), for a `division` or `zone`: answers by kind, response codes, average and p50/p95 time, by type and protocol, top names, blocked names, clients, zones and countries; `list` with `page` and `limit` for a page of one list; `node` for another server of a cluster ([DNS analytics](https://docs.dnzwithazee.com/dns/analytics.md)) |
| `threat_findings` | What threat detection found, newest first: `limit` per page (50, at most 200), `page`, and the `total` that match |
| `proxy_analytics` | DNZ Proxy traffic: totals, active users now (each visitor once, per proxy too), most requested URLs per hostname and path, top lists, and the web application firewall's blocked and logged requests with its top rules, addresses and paths; with `peaks` (a proxy counts [peak concurrency](https://docs.dnzwithazee.com/proxy/dnz-proxy.md#peak-concurrency)) the most requests in progress and connections open at once (`peak_inflight`, `peak_connections`: at most, the nodes' peaks added up) |
| `proxy_visitors` | Distinct visitors of each hostname DNZ Proxy serves over the last hours, total and per hour |
| `proxy_request_log` | DNZ Proxy request log lines, filtered by hostname, status, client or text |
| `proxy_routes` | Routes as [Proxy → Routes](https://docs.dnzwithazee.com/proxy/overview.md#adding-a-route) lists them, one row per hostname across proxies with each proxy's copy (HTTP, TCP and UDP): servers or the upstream they use, HTTPS and certificate progress, DNS, switches (custom headers, sign-in, upload limits, the web application firewall and rules included), zone and group. Every proxy's, or some (`proxies`), or the [private or public](https://docs.dnzwithazee.com/proxy/overview.md#private-and-public-proxies) ones' (`access`), each proxy's `access` listed; `search`, `types` (any of them, `tcp` and `udp` included), `groups` (any of them), `zone` and `view` (`zones` or `groups`) run before paging; a page at a time (`page`, `limit`, 50) with `total` |
| `proxy_upstreams` | Where routes send visits, one list for every proxy: servers, balancing, health check, each server's [health](https://docs.dnzwithazee.com/proxy/upstreams.md#server-health) as the DNZ Proxy nodes see it (how many find it up, and why the others don't), the [connection settings](https://docs.dnzwithazee.com/proxy/upstreams.md#connection-settings) (server name, CAs, client certificates without their keys, timeouts) and what uses each upstream; for servers from a [DNS name](https://docs.dnzwithazee.com/proxy/upstream-discovery.md), the name (`discovery`), what DNZ found (`discovered`) and the servers the proxies get now (`servers_now`). A page at a time (`page`, `limit`, 50) with `search`, `protocol` and `unused`, the `counts` and the `twins` (upstreams with the same servers) |
| `discover_proxy_upstream` | Looks an upstream's DNS name up now and answers the servers it gives, saving nothing; for `tasks.<service>` looked up by each proxy, what each DNZ Proxy node reported ([Servers from a DNS name](https://docs.dnzwithazee.com/proxy/upstream-discovery.md)) |
| `proxy_middlewares` | What happens to a visit on its way, one list for every proxy: each [middleware](https://docs.dnzwithazee.com/proxy/middlewares.md)'s type and settings (password hashes left out), HTTP or TCP, the proxy kinds that run it and what uses it (routes per proxy, chains, route defaults, not used). It can list only the firewalls with their rules, add routes' own switches, or show one by name |
| `proxy_config_export` | A proxy's configuration as the YAML file kept in Git ([configuration as code](https://docs.dnzwithazee.com/proxy/config-as-code.md)); passwords by user name only |
| `proxy_config_plan` | What applying a proxy YAML file would change, or why it can't be applied. Writes nothing: people apply |
| `proxy_config_import_nginx` | An Nginx configuration read as text into a proxy YAML file, what didn't map listed with its file and line. Nothing runs, nothing is stored or applied |
| `proxy_nginx` | An Nginx proxy's `dnz-nginx-agent`: connected or not, its version and Nginx's, the sites it read; managed: the last check and reload, what Nginx can't take, the file DNZ builds |
| `proxy_waiting_rooms` | DNZ Proxy waiting rooms now: visitors in, the limit and visitors waiting, per hostname |
| `proxy_waf_rules` | The [web application firewall](https://docs.dnzwithazee.com/proxy/waf.md)'s rules (id, category, level, what each catches), optionally only those a sensitivity runs |
| `proxy_waf_hits` | Requests the web application firewall matched since each node started, per proxy, middleware, rule and action (`block` or `log`). Every `proxy_waf_*` tool that reads, `proxy_bot_traffic` and `decoy_touches` take `access` (`private`, `public`) to look at those proxies only ([The WAF page](https://docs.dnzwithazee.com/proxy/waf-page.md#private-or-public-proxies)) |
| `proxy_waf_shields` | [Shields](https://docs.dnzwithazee.com/proxy/shields.md) for apps that can't be upgraded: each one's CVE, product, what it stops and costs, the routes with it on and what it stopped; routes that seem to run a shield's product; nodes too old to run them |
| `proxy_waf_bans` | Addresses [banned](https://docs.dnzwithazee.com/proxy/waf-page.md#bans-that-expire) now: reason, when it ends, where and who made it; each proxy's ban settings (paged) |
| `zone_decoys` | A zone's [decoy names](https://docs.dnzwithazee.com/proxy/decoys.md): on or off, the DNZ Proxy, the ban, each name's state |
| `decoy_touches` | Who touched a decoy name in the last 30 days: visits, which ban, and DNS queries, which only alert (paged) |
| `proxy_waf_lists` | The [lists of bad networks](https://docs.dnzwithazee.com/proxy/waf-page.md#lists-of-bad-networks): size, last update, terms and the firewalls naming each |
| `proxy_bot_traffic` | Humans, verified bots, AI crawlers and fake bots over a span on [routes that tell them apart](https://docs.dnzwithazee.com/proxy/waf-page.md#bots): totals, over time, per hostname, the top crawlers, the fake ones' addresses; with `catalog` the names DNZ knows |
| `proxy_waf_leaked_passwords` | [Leaked password checks](https://docs.dnzwithazee.com/proxy/leaked-passwords.md): the filter's source, size, last update and terms, the routes with the check on and what each node serving them checked |
| `proxy_waf_campaigns` | Attacks [grouped by network](https://docs.dnzwithazee.com/proxy/waf-page.md#campaigns), across hostnames: addresses, requests, hostnames, attack types, times (paged) |
| `try_firewall_rule` | A firewall rule [tried on past traffic](https://docs.dnzwithazee.com/proxy/waf-page.md#try-a-firewall-rule-on-past-traffic) before saving it, nothing written: matches in the sample, the share of traffic it holds, an estimate marked as one, what the log can't answer |
| `proxy_waf_previews` | Routes [trying a higher sensitivity](https://docs.dnzwithazee.com/proxy/waf-page.md#preview-a-higher-sensitivity): what each would also have stopped, in words (paged) |
| `proxy_waf_report` | What the [weekly WAF report](https://docs.dnzwithazee.com/proxy/waf-page.md#the-weekly-report) says of the last seven days. Sending it is left to people |
| `proxy_nodes` | DNZ Proxy nodes and their servers: state, version, uptime, upstreams, and the node's CPU and memory, the machine's or container's memory, load average and free disk; a page of them (`page`, `limit`, `proxy`) with the total and how many wait for approval, or one `node` |
| `proxy_http3` | Where visitors get [HTTP/3](https://docs.dnzwithazee.com/proxy/dnz-proxy.md#http3), per proxy, and how each kind switches it: a DNZ Proxy's HTTPS entry points with the nodes that have the UDP port open, why others can't, and requests and connections served; a Caddy's switch; a Traefik's entry points read from its API. A DNZ Proxy's routes with [HTTP/3 off](https://docs.dnzwithazee.com/proxy/dnz-proxy.md#http3-per-route) and nodes too old for that. Read-only: turning it on means opening a port; a route's off goes through `bulk_edit_routes` (`set_switch`, `http3`) |
| `proxy_node_updates` | [Updating DNZ Proxy nodes](https://docs.dnzwithazee.com/proxy/dnz-proxy.md#updating-nodes), per proxy: what an update would do for each node (`ready`, `current`, `by_hand` with the command, `offline`), the update under way node by node, and past ones with why one stopped. Read-only: starting one is for people |
| `proxy_sites` | [Read-only proxies](https://docs.dnzwithazee.com/proxy/read-only.md) (a Caddy or Traefik DNZ only reads): their division, API, what DNS answers with, records mode, sync state and last error, and every hostname of their last read with its status |
| `cloudflare_compare` | A local zone beside the Cloudflare zone of the same name, one row per name and type with both values and a status (same, split, only local, only Cloudflare, conflict) ([Compare with Cloudflare](https://docs.dnzwithazee.com/integrations/cloudflare.md#compare-with-cloudflare)) |
| `cloudflare_zone` | A Cloudflare zone's traffic, SSL/TLS and security settings, DNSSEC and tunnel hostnames, each part on its own: a part the token has no permission for is `{ missing: "Zone → Analytics → Read" }` (the permission to add), and `missing_permissions` lists every one Cloudflare refused so far. Only with `dnz mcp` (stdio): assistants signed in over OAuth can't reach Cloudflare connections ([Token permissions](https://docs.dnzwithazee.com/integrations/cloudflare.md#token-permissions)) |
| `encrypted_dns` | Encrypted DNS: whether DoT, DoH, DoQ and DoH3 are on, the addresses devices use, the certificate in use and, for superadmins, the certificate DNZ issues itself |
| `audit_log` | Who changed what, newest first; `page` and `limit` (20 a page) |
| `list_name_findings` | What DNZ found looking at [its own names](https://docs.dnzwithazee.com/dashboard/name-checks.md): dangling CNAMEs, names anyone can claim, origins reachable without the proxy, exposed files, each with how to fix it, and how the last round went; `state`, `kind`, `search`, `page` and `limit` (20 a page). `origin_unknown` never means safe. Read-only |
| `failover_drills` | The [failover drills](https://docs.dnzwithazee.com/dashboard/failover-drills.md) set up: whether they are switched on, what each stage does, the run under way, each drill's window, schedule and last result; `search`, `page` and `limit` (20 a page). Read-only: people start drills |
| `failover_drill_runs` | Past drills, newest first, each with its report: time to notice, time until traffic moved, errors visitors saw, what didn't go as set; `drill`, `status`, `page` and `limit` (20 a page). Read-only |
| `list_changes` | [What changed](https://docs.dnzwithazee.com/dashboard/what-changed.md) around an incident or in a window: records, routes, firewall and WAF rules, certificates, nodes, settings and earlier incidents on one timeline, each line only if the caller may read its source, lines about `near` or the `incident` first; `page` and `limit` (20 a page). It shows what happened before and never what caused it. Read-only |
| `pending_changes` | Change requests waiting for approval (or, with `status`, decided ones); `page` and `limit` (20 a page) |
| `topology` | How divisions, zones, views, upstreams, proxies and replication fit together |
| `server_overview` | [Overview → Server](https://docs.dnzwithazee.com/dashboard/server.md): this server (superadmin), its secondaries with each DNZ secondary's numbers, or the primary it follows, and every proxy with its nodes or agent; a section you may not see is left out |
| `replication_status` | How far each replica is in sync ([Replication](https://docs.dnzwithazee.com/dns/replication.md#sync-status)): state, zones current and lagging with both serials, the other parts copied, and for a secondary of this server the last NOTIFY to each address (answered, refused, no answer) and the transfer errors it reported (superadmin) |
| `view_records` | [Views](https://docs.dnzwithazee.com/dns/views.md) with their networks and record counts, or one view's records (zone, name, type, value, TTL, on/off) in the zones you may read; one view's records a page at a time (`page`, `limit`, 50) |
| `list_status_pages` | Hosts with a status page ([Status pages](https://docs.dnzwithazee.com/proxy/status-pages.md)): kind, state, schedule, allowed addresses, proxies; a page at a time (`page`, `limit`, 50) unless `host` names one |
| `status_page_hosts` | Hosts a status page can be set on, with the proxies serving each |
| `private_ca` | DNZ's [private certificate authority](https://docs.dnzwithazee.com/proxy/private-ca.md): the issuer (subject), key type, intermediate, name constraints, revocation list, versions, where devices download the root and its SHA-256 fingerprint, and a page of the certificates it signed (`search`, `state`, `page`, `limit`); `root: true` adds the root's PEM. Never a private key; issuing is left to people |
| `list_uptime_monitors` | [Uptime](https://docs.dnzwithazee.com/dashboard/uptime.md) monitors: state and since when, what each checks, uptime over 24 hours, 7, 30 and 90 days, response time, last error, public name, the places it is checked from and what each saw; open incidents and watched route groups; a page of monitors at a time (`page`, `limit`, 50) unless `monitor` names one |
| `private_ca_acme` | The CA's [ACME server](https://docs.dnzwithazee.com/proxy/private-ca.md#acme): whether it is on, the directory address clients are given, whether signing up takes an external key, validity, counts, and a page of its `list`: `orders` (names, status, why one failed; `account`, `status`), `accounts` (what each may ask for; `search`, `status`) or `keys` (without their secrets). Read only: turning it on, keys and allow lists are left to people |
| `list_uptime_monitors` | [Uptime](https://docs.dnzwithazee.com/dashboard/uptime.md) monitors: state and since when, what each checks, uptime over 24 hours, 7, 30 and 90 days, response time, last error, public name; open incidents and watched route groups; a page of monitors at a time (`page`, `limit`, 50) unless `monitor` names one |
| `list_uptime_incidents` | Uptime incidents (automatic and written by people): status, impact, affected monitors, duration and timeline; `page` and `limit` (20 a page) |
| `proxy_incidents` | [Proxy incidents](https://docs.dnzwithazee.com/integrations/notifications.md#proxy-alerts) going on now (nodes, origins, attack spikes, with when each started) and the latest incidents and recoveries, certificates included; the latest a page at a time (`page`, `limit`, 20, with `total`) |
| `create_record` ✎ | Adds a record |
| `update_record` ✎ | Changes a record by id; fields left out stay |
| `delete_records` ✎ | Deletes records of one zone by id, all or none |
| `create_zone` ✎ | Creates an empty primary zone |
| `set_typo_help` ✎ | Sets a zone's [typo help](https://docs.dnzwithazee.com/dns/typo-help.md): the mode, the fallback DNZ Proxy and the suggested sites |
| `clear_dns_cache` ✎ | Empties the forwarding and recursive caches (superadmin) |
| `set_active_upstream` ✎ | Makes one forwarding upstream the one in use: first in the order, the others its fallbacks, strategy failover ([Set an upstream active](https://docs.dnzwithazee.com/dns/forwarding.md#set-an-upstream-active)) |
| `clear_proxy_cache` ✎ | Drops what a DNZ Proxy's nodes cached for a hostname (superadmin) |
| `filter_history` | What changed in DNS filtering, `page` and `limit` (20 a page), by `division`, `kind`, `who` and `query`: who changed what when, part by part; with `id`, that line with its domain lines and what undoing it would do now. Undoing is left to people ([History](https://docs.dnzwithazee.com/dns/filtering.md#history)) |
| `proxy_route_history` | A proxy's route versions (or one hostname's), `page` and `limit` (20 a page): who changed which route when, and what changed part by part; with `version`, the route before and after and what restoring would change. Restoring is left to people ([Route history](https://docs.dnzwithazee.com/proxy/route-history.md)) |
| `proxy_route_group` ✎ | Puts a hostname's route in a group (Web app, REST API, …) on every proxy serving it (or one), or in none (`group: ""`); only sorts the Routes page |
| `bulk_edit_routes` ✎ | [Several routes at once](https://docs.dnzwithazee.com/proxy/overview.md#several-routes-at-once), all or nothing: status pages, on/off, group, HTTPS, switches, shields, shared middlewares, one upstream, Serve on (`"private"`, `"public"` or `"all"` in `serve_on`: every approved proxy of that access now) and delete, on the routes holding the given `hosts`, on every proxy serving them (or one `proxy`). `operations` in order, e.g. `[{"op": "disable"}, {"op": "set_group", "group": "Web app"}]`; `dry_run` only checks and plans. Never passwords or sign-in |
| `set_status_page` ✎ | Sets or replaces a host's status page: maintenance, moved, blocked…, with a schedule and allowed addresses; `moved` is a 301 unless `permanent: false` (a 302). With `hosts`, every hostname of those routes on every proxy, in one batch ([several routes at once](https://docs.dnzwithazee.com/proxy/status-pages.md#several-routes-at-once)) |
| `maintenance_windows` | [Scheduled maintenance](https://docs.dnzwithazee.com/proxy/status-pages.md#scheduled-maintenance) that repeats: days, start time (in the server's time zone), length, hostnames, whether it is on now and when next; `upcoming_days` for the occurrences of the coming days |
| `set_maintenance_window` ✎ | Adds or changes (by `name`) scheduled maintenance: `hosts`, `days` (`sun`, `mon-fri`), `at` (`22:00`), `duration` (`2h`), first and last day (`from`, `until`), the page's title and message, whether uptime stays quiet (`quiet_alerts`). Deleting is left to people; one-off maintenance is `set_status_page` with a start and end |
| `switch_status_page` ✎ | Turns a host's status page on or off |
| `delete_status_page` ✎ | Removes a host's status page |
| `set_proxy_upstream` ✎ | Makes or changes an upstream (servers, or `from_dns` as `NAME:PORT` with `resolve_on`, `scheme` and `family` for servers from a DNS name; balancing, sticky sessions, health check); the change reaches every route using it, on every proxy. Deleting and merging are left to people |
| `set_proxy_middleware` ✎ | Makes or changes a middleware or firewall from Traefik's middleware object; the change reaches every route running it, on every proxy. Passwords and sign-in (basic auth, digest auth, sign-in, forward auth) are refused: they are for people. Deleting and merging are left to people |
| `set_route_redirect` ✎ | Redirects every visit to a route's hostnames to another address (`to`, `keep_path`, `permanent`), or removes the redirect (`off`); the rest of the route stays |
| `set_route_rules` ✎ | Sets a route's [rules](https://docs.dnzwithazee.com/proxy/rules.md) (redirects, rewrites and header changes when conditions hold, in order), or removes them (`off`); the rest of the route stays. Answers with each rule in plain words; a rule a Traefik or Caddy can't run is refused with its number |
| `set_waf` ✎ | Sets or turns off a DNZ Proxy route's web application firewall (`mode`, `sensitivity`, `skip_paths`, `skip_rules`, `allow_ips`); the rest of the route stays. Refused on Traefik and Caddy |
| `set_ai_crawlers` ✎ | [What AI crawlers may do](https://docs.dnzwithazee.com/proxy/waf-page.md#what-a-route-lets-ai-crawlers-do) (`allow`, `block`, `robots`) on the routes of some hostnames or every DNZ Proxy route |
| `set_shields` ✎ | Sets a route's [shields](https://docs.dnzwithazee.com/proxy/shields.md) (a list of ids; empty takes them off); the rest of the route stays. On Traefik and Caddy they stay off, and the answer's `warnings` says so |
| `waf_ban` ✎ | Refuses one address for a while (`minutes`, 60 by default), on every DNZ Proxy or one; it comes back in by itself ([bans](https://docs.dnzwithazee.com/proxy/waf-page.md#bans-that-expire)) |
| `waf_unban` ✎ | Lets a banned address back in now |

✎ marks the tools that change DNS or what visitors see. They are only offered with **read and change** access (OAuth),
`--allow-writes` (stdio) or an API token. A change that needs approval isn't made: the tool answers
with the change request's number instead. No tool approves changes, deletes zones, rolls back or restores a route version.

Each zone is also a resource, `dnz://zone/<name>`, with its zone file. And there are three prompts
(slash commands in clients that show them): `audit_zone`, `troubleshoot_name` and `recent_changes`.

## What an OAuth sign-in may do

The token an assistant gets acts as the user who allowed it, with their divisions and roles, and:

* **Read only** reads what that user can read in the API, except accounts, sessions, users, two-factor,
  OAuth apps, API tokens and division members, DNS update keys, backups, settings, notification
  providers, Cloudflare connections, ACME credentials, the server's system and setup pages, the
  cluster, and the private CA's keys (a certificate's private key and the CA's export). Proxies,
  routes, upstreams, middlewares, route groups, maintenance windows and the rest are readable. It
  may also make the calls that only look: a live capture, planning a proxy's YAML (never applying
  it), checking a route's rules and looking an upstream's DNS name up (nothing saved).
* **Read and change** also creates, changes and deletes records, creates zones and changes a zone's
  settings (typo help), clears the DNS cache and a proxy's cache, sets, switches and removes status
  pages, edits a route (its upload limits, redirect, web application firewall and rules, group),
  edits several routes at once (batch edit and *Set status page*), saves the route groups, and
  creates and changes scheduled maintenance windows, upstreams and middlewares. DNZ still refuses
  an assistant passwords and sign-in on routes and middlewares.
  Nothing else: no zone deletion, no rollbacks, no approvals, no settings, no applying a proxy's
  YAML, no new or deleted routes, and no deleting or merging maintenance windows, upstreams or
  middlewares.

Access tokens last an hour; the assistant renews them with a refresh token that lasts 30 days from
its last use and is replaced every time. **Account → Connected apps** (your name in the top bar)
lists the apps you allowed, with their access and last use, and signs them out; so does
`dnz mcp apps` / `dnz mcp revoke <id>` ([CLI](https://docs.dnzwithazee.com/cli/mcp.md)). Changing your password, a superadmin resetting it, or the
account being switched off signs every app out.

## How it works

* **Discovery**: an unauthenticated request to `/mcp` gets `401` with `WWW-Authenticate: Bearer
  resource_metadata=…` pointing at `/.well-known/oauth-protected-resource/mcp` (RFC 9728), which names
  this server as the authorization server; `/.well-known/oauth-authorization-server` (RFC 8414) has the
  endpoints.
* **Registration**: clients register themselves at `/oauth/register` (RFC 7591) as public clients (no
  secret). Redirect URIs must be `https://`, `http://` on localhost / 127.0.0.1 / \[::1] (any port), or an
  app's own scheme (`cursor://…`). Apps nobody approved are forgotten after a day.
* **Authorization**: `/oauth/authorize` checks the request and sends you to the consent page at
  `/oauth/consent`; PKCE with `S256` is required, codes last five minutes and work once. Scopes are
  `dnz:read` and `dnz:write`.
* **Tokens**: `/oauth/token` (authorization code and refresh token grants), `/oauth/revoke` (RFC 7009).
  Access tokens start with `dnzo_`, refresh tokens with `dnzr_`; only their SHA-256 is stored.
* **MCP**: `POST /mcp` takes one JSON-RPC message (or a batch) and answers with JSON; no sessions and
  no server-sent stream (`GET` is 405). Protocol revisions 2025-11-25, 2025-06-18, 2025-03-26 and
  2024-11-05. The tools call the API over this machine's loopback with your token; the audit log still
  shows the assistant's own address.
* The metadata uses the scheme and host the request came in on, so put DNZ behind your TLS proxy (or
  give it a certificate) and connect with that address. Most clients refuse to sign in over plain HTTP
  except on localhost.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.