Skip to content
RedundantDNS
Menu

MCP server

RedundantDNS exposes every dashboard capability as an MCP tool at <public URL>/mcp (for example https://app.redundantdns.com/mcp). It uses the official MCP Go SDK over Streamable HTTP, stateless: each POST is served on its own, with JSON responses, so any instance answers any request and there is no session to keep (GET and DELETE answer 405).

Connecting from Claude

  1. In Claude (claude.ai or Claude Desktop): Settings → Connectors → Add custom connector, URL https://<your host>/mcp.
  2. Claude discovers the authorization server from the 401 challenge (WWW-Authenticate: Bearer resource_metadata=".../.well-known/oauth-protected-resource/mcp"), registers itself (dynamic client registration) and opens the consent page.
  3. Sign in, pick the organization and the permissions, approve. Claude receives an access token (1 hour, refreshed automatically).

Clients whose redirect hosts are all in RDNS_OAUTH_TRUSTED_DOMAINS (default claude.ai,claude.com) skip the consent screen after the first approval of the same scopes for the same organization. Any other client always asks.

With a personal access token

Clients that take a static header (Claude Code, scripts, CI) can use a personal access token created in Settings → API tokens:

claude mcp add --transport http redundantdns https://<your host>/mcp \
  --header "Authorization: Bearer rdns_..."

A dashboard session cookie never authorizes /mcp.

Authorization

ScopeAllows
zones:readread zones, records, status, delegation checks, alerts and the audit log
zones:writechange zones, records and attachments, run reconcile/verify/adopt, resolve alerts, test alert channels
connections:readlist provider connections
connections:writecreate and test provider connections

A tool call needs its scope and the user's role in the organization (the same rule as /v1: viewers read, editors change records, admins manage connections, attachments and zone deletion). The organization is the token's (a PAT is bound to one org; an OAuth grant to the org picked at consent). The org and token IP allowlists apply, with the client IP resolved behind trusted proxies (RDNS_TRUSTED_PROXIES).

MCP is a plan feature (Pro and above, see docs/api.md): on other plans every /mcp request answers 402 plan_limit_reached with the plan that unlocks it. Tool calls that hit a plan limit (a zone over the limit, a third provider) return error plan_limit_reached: ... with the details.

Errors come back as tool errors (isError: true) with the API's error code, for example error insufficientScope: the token lacks the connections:read scope or error apexNsManaged: the apex NS set is managed by the platform.

Tools

ToolScopeWhat it does
zone_listzones:readlist zones (attachments, NS plan, status; no records)
zone_getzones:readone zone with records, attachments, capabilities and status
zone_createzones:writecreate a canonical zone; parentDelegation (on by default when a parent zone of the org exists) writes its NS delegation into that parent
zone_deletezones:writedelete the canonical zone; confirmName must repeat the zone name (admins)
record_listzones:readrecord sets of a zone (managedBy: "delegation" marks a child zone's delegation, read-only)
record_upsertzones:writecreate or replace a record set (previousName/previousType rename one) and reconcile
record_deletezones:writedelete a record set and reconcile
provider_connection_listconnections:readprovider connections (credentials never returned)
provider_connection_createconnections:writetest and save a connection: BYO credentials, or mode: managed (admins; acceptManagedTerms: "<version>" accepts the Managed Provider Terms once for the org, otherwise the call fails with managed_terms_required)
provider_connection_testconnections:writere-test a stored connection
zone_attach_providerzones:writeattach a connection (creates or adopts the provider zone) (admins)
zone_detach_providerzones:writedetach; deleteRemote + confirmName deletes the provider zone (admins)
zone_reconcilezones:writepush the canonical zone to the providers now
zone_verifyzones:writecompare providers with the canonical zone now
zone_adopt_changeszones:writeimport one provider's records into the canonical zone
zone_delegation_checkzones:readcheck the delegation (parent NS vs NS plan) now
zone_statuszones:readsync state per attachment and the last delegation check
audit_listzones:readlatest audit events, optionally for one zone
audit_exportzones:readevery audit event of a period (from, to, optional zoneId), oldest first; admins, Business plan and above
audit_verifyzones:readverify the tamper-evident audit stream of a period (from, to: days): hash chain, signed seals, platform inclusion; admins
compliance_reportzones:readthe organization's compliance posture now (profile: baseline, iso27001, soc2 or an extra one): each control's status, evidence, ISO 27001 / SOC 2 mapping and remediation; record: true also records it in the audit log and the audit stream; admins (docs/compliance.md)
alert_listzones:readalert history (drift, sync errors, delegation, providers down or degraded, zones not verified); filters zoneId, rule, state, limit
alert_resolvezones:writeresolve a firing alert by hand (editors); the channels are notified
alert_channel_testzones:writesend a test notification to an alert channel now (admins)
billing_getzones:readthe plan, billing status, limits with usage and the managed pass-through of the period (upgrades happen in the dashboard)
license_listzones:readthe self-hosted licenses issued to the organization: standing (active, suspended, revoked), product, edition, mode, term, features, limits and validity window (never the license itself)
domain_listdomains:readthe organization's domains at the platform's registrar: status, expiry, lock, auto-renewal, nameservers and whether they match the zone's NS plan
domain_getdomains:readone domain with its registrant, transfer progress, linked zone and registrar jobs not done yet
domain_checkdomains:readavailability and price (priceCents, currency, years) of up to 20 names; names held on the platform are not available
domain_registerdomains:writeregister a new domain (admins): the domain waits in payment_pending and the answer's checkoutUrl is the payment page to give the user; the registration runs once paid (registering, then active or registration_failed)
domain_set_nameserversdomains:writeset the nameservers at the registrar (editors)
domain_apply_zone_nsdomains:writewrite a zone's NS plan (default: the zone with the domain's name) as the nameservers: the step that makes the domain redundant
domain_transfer_indomains:writetransfer a domain in with its auth code (admins); the owner is the registrant profile or contactId; acceptDomainTerms accepts the Domain Registration Terms in the call
domain_auth_codedomains:writereveal the transfer-out auth code (admins; audited; never gated)
domain_lockdomains:writeturn the transfer lock on or off (admins; unlocking is never gated)

Tool names match ^[a-zA-Z0-9_-]{1,64}$ (Claude rejects the whole list otherwise); internal/mcp tests enforce it and that the catalog matches this table.

Audit

Every tool call is written to the org's audit log as mcp.tool.call (source mcp, target mcpTool/<name>) with a summary of the arguments, the resulting HTTP status and error code, the auth kind (pat or oauth) and the OAuth client id. Values under credential-like keys (credentials, secret, password, token, privateKey, authCode...) are never logged: only their field names. The action the tool performed (for example zone.create) is audited as usual, with source mcp.

Design notes

  • A tool does not re-implement the product: after its scope check it runs the matching /v1 request in process as the caller, so validation, role checks, capability intersection and audit are exactly the API's. Adding a /v1 capability means adding its tool in internal/mcp/tools.go.
  • Each POST builds a fresh MCP server bound to the caller (the tool schemas are cached), which is what makes the endpoint stateless.
  • DNS-rebinding protection of the SDK is off: /mcp only accepts bearer tokens, and the check would reject a reverse proxy on the same host.

Try it with curl

curl -s -X POST https://<host>/mcp \
  -H "Authorization: Bearer rdns_..." -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

curl -s -X POST https://<host>/mcp ... \
  -d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"zone_create","arguments":{"name":"example.com"}}}'