Content
# gandi-mcp — Gandi LiveDNS MCP Server
[](https://www.npmjs.com/package/@themkn/gandi-mcp)
[](./LICENSE)
An [MCP server](https://modelcontextprotocol.io) for [Gandi LiveDNS](https://api.gandi.net/docs/livedns/). Manage DNS records, list domains, and take zone snapshots — directly from [Claude Code](https://docs.anthropic.com/en/docs/claude-code) or any MCP-compatible client.
## What this gives you
- **8 MCP tools:** `list_domains`, `list_records`, `get_record`, `add_record`, `update_record`, `delete_record`, `list_snapshots`, `create_snapshot`
- **Safety by default:** every `add`/`update`/`delete` writes a timestamped local JSON backup of the full zone **before** mutating. Configurable.
- **One-JSON config:** no env vars, one file with strict `0600` perms
## Prerequisites
- **A Gandi account**
- **Node.js 24+**
### Get a Gandi Personal Access Token
Gandi replaced legacy API keys with Personal Access Tokens (PATs). To create one for `gandi-mcp`:
1. Sign in at [admin.gandi.net](https://admin.gandi.net/).
2. Top-right menu → **User settings** → **Authentication options** (or visit [account.gandi.net/security](https://account.gandi.net/security)).
3. Under **Personal Access Tokens**, click **Create a token**.
4. Give it a name (e.g. `gandi-mcp`) and pick an expiration (max 1 year — Gandi will email you when it nears expiry).
5. **Scope — which domains:** pick *Specific organizations* or *Specific domains* and select the ones you want this MCP to manage. Don't grant access to the whole account if you don't need it.
6. **Permissions:** enable **"Manage domain technical configurations"** (covers LiveDNS read + write). If any of your domains are owned by a Gandi *organization* (not your personal account), also enable **"View organization information"** so `list_domains` can discover them — otherwise it will silently fall back to your personal-org domains only. Everything else (billing, transfers, etc.) can stay unchecked.
7. Click **Create**. **Copy the token immediately** — Gandi only shows it once. If you miss it, delete and create a new one.
That token is your `apiKey` value in the config below.
## Install
```sh
npm install -g @themkn/gandi-mcp
```
The binary is `gandi-mcp` regardless of the scoped package name.
## Configure
The server reads a JSON config from `~/.gandi-mcp/config.json`. Create it with `0600`:
```sh
mkdir -p ~/.gandi-mcp
chmod 700 ~/.gandi-mcp
cat > ~/.gandi-mcp/config.json <<'EOF'
{
"apiKey": "YOUR_GANDI_PAT",
"defaultDomain": "example.com"
}
EOF
chmod 600 ~/.gandi-mcp/config.json
```
### Full config reference
| Field | Required | Default | Description |
|-------|----------|---------|-------------|
| `apiKey` | yes | — | Your Gandi Personal Access Token |
| `defaultDomain` | no | none | Used when a tool call omits `domain` |
| `autoBackup` | no | `true` | Write local zone backup before each mutation |
| `backupDir` | no | `~/.gandi-mcp/backups` | Where backup files land |
The server refuses to start if the config file is group- or world-readable.
## Hook into Claude Code
Add this to your `.mcp.json` (project-scoped) or `~/.config/claude/mcp.json` (global):
```json
{
"mcpServers": {
"gandi": {
"command": "gandi-mcp"
}
}
}
```
### Recommended permissions
By default Claude Code asks for permission on every Gandi tool call. A blanket
`mcp__gandi__*` allow works, but it also auto-approves irreversible live DNS
changes like `delete_record` and `update_record`. A tiered policy keeps the
inspection flow frictionless while making destructive zone edits explicit.
Add this to your `~/.claude/settings.json`:
```json
{
"permissions": {
"allow": [
"mcp__gandi__list_domains",
"mcp__gandi__list_records",
"mcp__gandi__get_record",
"mcp__gandi__list_snapshots",
"mcp__gandi__create_snapshot",
"mcp__gandi__add_record"
]
}
}
```
What this does:
- **Reads** (`list_domains`, `list_records`, `get_record`, `list_snapshots`) run
without prompting — they can't change anything.
- **Additive writes** (`create_snapshot`, `add_record`) also run without
prompting. Snapshots are purely additive, and the server takes an automatic
local zone backup before every record mutation, so a stray `add_record` is
easy to recover from.
- **Destructive writes** (`update_record`, `delete_record`) are *not* listed, so
Claude Code will prompt before each call. The auto-backup helps recover the
previous record values, but DNS caches still propagate — a bad change can
break a live site for as long as the old TTL lasts. These should be
deliberate.
If you want a stricter setup, keep a wildcard `allow` and add a `deny` block for
the destructive tools — `deny` blocks the call entirely (no prompt, no
override):
```json
{
"permissions": {
"allow": ["mcp__gandi__*"],
"deny": [
"mcp__gandi__update_record",
"mcp__gandi__delete_record"
]
}
}
```
## Tool reference
| Tool | What it does |
|------|--------------|
| `list_domains` | List LiveDNS-managed domains. Auto-discovers organizations the user belongs to and unions their domains with the personal-org list, deduped by FQDN. Pass `sharing_id` to scope to a single organization. |
| `list_records` | List records for a domain, with optional `type` and `nameFilter` (anchored, case-insensitive glob — `*` = any, `?` = one) |
| `get_record` | Fetch a single record by name + type |
| `add_record` | Add a new record; auto-backup runs first |
| `update_record` | Replace values/TTL on an existing record; auto-backup runs first |
| `delete_record` | Delete a record; auto-backup runs first |
| `list_snapshots` | List Gandi server-side zone snapshots |
| `create_snapshot` | Create a server-side zone snapshot on Gandi |
Mutation tools return the local backup path in their response — reference it if you need to roll back manually.
## Backups
Each mutation writes `~/.gandi-mcp/backups/<domain>-<timestamp>.json`:
```json
{
"domain": "example.com",
"date": "2026-04-24T11:47:03.123Z",
"records": [ /* full zone */ ]
}
```
Format matches the `gandi` CLI's backup shape. Disable via `"autoBackup": false` in config.
## Development
```sh
git clone git@github.com:themkn/gandi-mcp.git
cd gandi-mcp
pnpm install
pnpm test
pnpm build
```
## License
MIT