Skip to main content
Glama
README.md
# cloudflare-dns-mcp

An [MCP](https://modelcontextprotocol.io) server that lets an LLM check, claim, release, and
list subdomains under a root domain, backed by the Cloudflare DNS API. Runs over stdio
(local, default) or streamable HTTP (remote, behind a bearer token).

## Tools

- **check_subdomain_availability** — check whether `<subdomain>.<root_domain>` already has a DNS record.
- **request_domain** — claim `<subdomain>.<root_domain>` by creating an A record pointing at an IPv4 address. Fails if the subdomain is already taken.
- **release_subdomain** — delete the DNS record for `<subdomain>.<root_domain>`, freeing it up again.
- **list_subdomains** — list every record under `<root_domain>` currently registered.

## Setup

```bash
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
```

Copy `.env.example` to `.env` and fill in your Cloudflare credentials:

```bash
cp .env.example .env
```

| Variable | Required | Description |
| --- | --- | --- |
| `CLOUDFLARE_API_TOKEN_DNS` | yes | Cloudflare API token with `Zone:DNS:Edit` permission for the target zone. |
| `CLOUDFLARE_ZONE_ID` | yes | Zone ID of the domain in Cloudflare. |
| `CLOUDFLARE_ROOT_DOMAIN` | yes | Root domain that subdomains are created under, e.g. `example.com`. |
| `CLOUDFLARE_API_BASE_URL` | no | Overrides the Cloudflare API base URL. Defaults to `https://api.cloudflare.com/client/v4`. |

## Running

By default the server runs over stdio, meant to be launched by an MCP client rather than
run standalone:

```bash
python main.py
```

To run it as a standalone remote server over streamable HTTP instead, set `MCP_TRANSPORT=http`
plus a shared-secret `MCP_HTTP_TOKEN` (see `.env.example`):

```bash
MCP_TRANSPORT=http MCP_HTTP_HOST=0.0.0.0 MCP_HTTP_PORT=8000 MCP_HTTP_TOKEN=a-long-random-secret python main.py
```

`MCP_HTTP_TOKEN` is required in HTTP mode — the server refuses to start without it, since it
can create and delete DNS records and must not be exposed unauthenticated. Every request needs
`Authorization: Bearer <token>`; requests without it (or with the wrong token) get a 401. Put a
reverse proxy with TLS in front of it if it's reachable from the public internet.

### Claude Code

```bash
claude mcp add cloudflare-dns /path/to/.venv/bin/python /path/to/main.py \
  --env CLOUDFLARE_API_TOKEN_DNS=your-token \
  --env CLOUDFLARE_ZONE_ID=your-zone-id \
  --env CLOUDFLARE_ROOT_DOMAIN=example.com
```

Or add it by hand to `.mcp.json` (project scope) or `~/.claude.json` (user scope):

```json
{
  "mcpServers": {
    "cloudflare-dns": {
      "command": "/path/to/.venv/bin/python",
      "args": ["/path/to/main.py"],
      "env": {
        "CLOUDFLARE_API_TOKEN_DNS": "your-token",
        "CLOUDFLARE_ZONE_ID": "your-zone-id",
        "CLOUDFLARE_ROOT_DOMAIN": "example.com"
      }
    }
  }
}
```

If the server is running remotely over HTTP instead:

```bash
claude mcp add --transport http cloudflare-dns https://your-host/mcp \
  --header "Authorization: Bearer your-mcp-http-token"
```

or in `.mcp.json`:

```json
{
  "mcpServers": {
    "cloudflare-dns": {
      "type": "http",
      "url": "https://your-host/mcp",
      "headers": {
        "Authorization": "Bearer your-mcp-http-token"
      }
    }
  }
}
```

### opencode

Add to `opencode.json` (project) or `~/.config/opencode/opencode.json` (global):

```json
{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "cloudflare-dns": {
      "type": "local",
      "command": ["/path/to/.venv/bin/python", "/path/to/main.py"],
      "enabled": true,
      "environment": {
        "CLOUDFLARE_API_TOKEN_DNS": "your-token",
        "CLOUDFLARE_ZONE_ID": "your-zone-id",
        "CLOUDFLARE_ROOT_DOMAIN": "example.com"
      }
    }
  }
}
```

If the server is running remotely over HTTP instead, use `type: "remote"`:

```json
{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "cloudflare-dns": {
      "type": "remote",
      "url": "https://your-host/mcp",
      "enabled": true,
      "headers": {
        "Authorization": "Bearer your-mcp-http-token"
      }
    }
  }
}
```

### Other MCP clients

Generic stdio client config:

```json
{
  "mcpServers": {
    "cloudflare-dns": {
      "command": "/path/to/.venv/bin/python",
      "args": ["/path/to/main.py"],
      "env": {
        "CLOUDFLARE_API_TOKEN_DNS": "...",
        "CLOUDFLARE_ZONE_ID": "...",
        "CLOUDFLARE_ROOT_DOMAIN": "example.com"
      }
    }
  }
}
```

Generic streamable HTTP client config:

```json
{
  "mcpServers": {
    "cloudflare-dns": {
      "type": "http",
      "url": "https://your-host/mcp",
      "headers": {
        "Authorization": "Bearer your-mcp-http-token"
      }
    }
  }
}
```

## Security

Never commit your `.env` file — it contains a Cloudflare API token that can edit DNS records
for your zone. `.gitignore` already excludes it; only `.env.example` (with placeholder values)
is tracked.

If you run the HTTP transport, `MCP_HTTP_TOKEN` is the only thing standing between the
internet and DNS write access to your zone — treat it like the Cloudflare token itself
(long/random, not committed, rotated if it ever leaks), and put it behind TLS.