Skip to main content
Glama
README.md
# cloudflare-tools

Cloudflare tools for coding agents: zones, DNS, analytics, Workers, Pages, storage and tunnels.

| Harness        | Delivery                                       | Pinned by                      |
| -------------- | ---------------------------------------------- | ------------------------------ |
| OpenCode V2    | native plugin (npm package, with skills)       | the version in the package     |
| OpenCode V1    | native plugin (npm package, no skills)         | the version in the package     |
| Claude Code    | plugin: MCP server and skills                  | the release tag, as the ref    |
| Codex          | plugin: MCP server and skills                  | the release tag, as the ref    |
| Grok Build     | plugin: MCP server and skills                  | the release tag, as `@ref`     |
| Antigravity    | plugin: MCP server and skills                  | the release tag's tree URL     |
| Hermes Agent   | native plugin (Python, calls the command line) | the release commit, as the SHA |
| other MCP host | MCP server from npm                            | the version in the package     |

The tools need [Node.js](https://nodejs.org/) 24 or later.
[`docs/conventions.md`](docs/conventions.md) describes how the tools are named
and how they behave.

## Install

Install from a release, never from the default branch: a release is a tag whose
commit carries the built `dist/`, and the default branch does not. In the
commands below, `<version>` is a release's version (`1.2.3`) and `<sha>` is the
commit its tag names:

```sh
gh api repos/craft-agent/cloudflare-tools/commits/v<version> --jq .sha
```

### OpenCode

OpenCode V2 takes the package and its options in `opencode.json`:

```json
{
  "plugins": [{ "package": "@craftsamo/cloudflare-tools@<version>", "options": { "read_only": false } }],
  "permissions": [{ "action": "cloudflare_*", "resource": "*", "effect": "ask" }]
}
```

OpenCode V1 takes the package as a string, or as a pair with its options:

```json
{
  "plugin": [["@craftsamo/cloudflare-tools@<version>", { "read_only": false }]],
  "permission": { "cloudflare_*": "ask" }
}
```

OpenCode allows any action no rule names, so the plugin's request is granted
silently unless a rule says `ask`. Read tools never ask, so the `cloudflare_*` glob
is safe.

V1 registers no skills. Unpack the package and copy its `skills/*` folders into
`~/.config/opencode/skills/`:

```sh
npm pack @craftsamo/cloudflare-tools@<version>
tar -xzf craftsamo-cloudflare-tools-<version>.tgz
mkdir -p ~/.config/opencode/skills
cp -r package/skills/* ~/.config/opencode/skills/
rm -rf package craftsamo-cloudflare-tools-<version>.tgz
```

### Claude Code

```sh
claude plugin marketplace add craft-agent/cloudflare-tools#v<version>
claude plugin install cloudflare-tools@craft-agent
```

### Codex

```sh
codex plugin marketplace add craft-agent/cloudflare-tools --ref v<version>
codex plugin add cloudflare-tools@craft-agent
```

Codex turns `-` into `_` in server names, so it shows the tools as
`mcp__cloudflare_tools__cloudflare_*`. It starts the MCP server in the plugin's
install directory, which is never the caller's working tree.

### Grok Build

```sh
grok plugin install craft-agent/cloudflare-tools@v<version> --trust
```

Grok starts the MCP server in the session's working directory.

### Antigravity

Install from the tree of the release tag, never from the default branch, which
has no built `dist/`; Antigravity clones the tag into
`~/.gemini/config/plugins/cloudflare-tools`.

```sh
agy plugin install https://github.com/craft-agent/cloudflare-tools/tree/v<version>
```

### Hermes Agent

Hermes installs a commit, not a tag, and the plugin installs disabled:

```sh
hermes plugins install craft-agent/cloudflare-tools --ref <sha>
hermes plugins enable cloudflare-tools
```

If a Hermes gateway is already running, restart it (`hermes gateway restart`) to
load the plugin.

### Any other MCP host

Add the server to the host's MCP configuration. Flags go in `args`:

```json
{
  "mcpServers": {
    "cloudflare-tools": {
      "command": "npx",
      "args": ["-y", "@craftsamo/cloudflare-tools@<version>", "--read-only"]
    }
  }
}
```

## Tools and skills

Each toolset comes with a skill that tells an agent how to use its tools.

### Account (`account`)

Account credentials, token verification, and audit logs.

| Tool                      | What it does                                                                               | Class |
| ------------------------- | ------------------------------------------------------------------------------------------ | ----- |
| `cloudflare_account_read` | `view` account details and token status, `list` configured accounts, or query `audit` logs | read  |

The skill is [`cloudflare-account`](skills/cloudflare-account/SKILL.md), shown as
`cloudflare-tools:cloudflare-account` in Claude Code and
`cloudflare-tools:account` in Hermes.

### Zones (`zones`)

Zone lookup, settings (read and change), Universal SSL, certificates, and DNSSEC.

| Tool                              | What it does                                                                       | Class          |
| --------------------------------- | ---------------------------------------------------------------------------------- | -------------- |
| `cloudflare_zone_read`            | `list` zones, `view` details, read `settings`, Universal `ssl` status, or `dnssec` | read           |
| `cloudflare_zone_setting_edit`    | change 1–20 zone settings in order; refuses a change that lowers protection        | overwrite      |
| `cloudflare_zone_setting_weaken`  | change zone settings in ways that lower a visitor's protection; always asks first  | administrative |
| `cloudflare_zone_cache_purge`     | purge cached copies of up to 100 URLs, hosts, prefixes or tags in a zone           | idempotent     |
| `cloudflare_zone_cache_purge_all` | purge the whole cache of a zone; always asks first                                 | administrative |
| `cloudflare_zone_dnssec_enable`   | turn DNSSEC on for a zone; harmless until registrar publishes the DS record        | idempotent     |
| `cloudflare_zone_dnssec_disable`  | turn DNSSEC off for a zone; requires DS removed at registrar first                 | administrative |

The skill is [`cloudflare-zones`](skills/cloudflare-zones/SKILL.md), shown as
`cloudflare-tools:cloudflare-zones` in Claude Code and
`cloudflare-tools:zones` in Hermes.

## Adding a tool

1. Write the spec in `src/core/<domain>/<verb>.ts` with `defineTool`: an ID
   `<prefix>_<domain>_<verb>`, a title and description, the Zod argument and
   output schemas, its toolset and write class, and `execute(args, context)`.
   A write tool also gives `targets(args, context)`: what the call writes to,
   as a permission rule should name it.
2. Test it beside the spec (`<verb>.test.ts`), calling it through `invoke` as
   the entry points do. A tool that runs a program is tested against a fake of
   that program on `PATH`; no test reaches a real service.
3. List it in `src/tools.ts`. A new toolset goes into `toolsets` in
   `src/project.ts` (and `defaultToolsets` when it is on by default).
4. Add or update the toolset's skill, `skills/<prefix>-<toolset>/SKILL.md`.
5. Run `pnpm run build && pnpm run manifests` to list the tool in
   `plugin.yaml`, then describe it under "Tools and skills".

[`docs/conventions.md`](docs/conventions.md) has the rules each step follows.

## Reads and writes

Every read tool is named `<prefix>_<domain>_read`. Reads can run unasked;
writes are confirmed by the harness.

| Harness     | Reads without asking                                                                                                 |
| ----------- | -------------------------------------------------------------------------------------------------------------------- |
| OpenCode    | always; the plugin asks only before a write                                                                          |
| Claude Code | the rule `mcp__plugin_cloudflare-tools_cloudflare-tools__*_read` in `permissions.allow` of `~/.claude/settings.json` |
| Codex       | always; Codex runs tools that declare `readOnlyHint` unasked, as the read tools do                                   |
| Grok Build  | `grok --allow 'MCPTool(cloudflare-tools__*_read)' --allow 'MCPTool(cloudflare_*_read)'` (both rules; see below)      |
| Antigravity | the rule `mcp(cloudflare-tools_cloudflare-tools/<tool id>)` in `permissions.allow`, one for each read tool           |
| Hermes      | always; the plugin asks only before a write                                                                          |

Where a write is asked about, and where it is not:

- **OpenCode** grants any action no rule names, so a write runs unasked unless
  a rule asks for it: add the `cloudflare_*` rule shown under [OpenCode](#opencode).
- **Codex** under `codex exec`, and **Grok Build** under `grok -p`, refuse a
  write they cannot ask about, unless it is allowed beforehand.
- **Grok Build** calls the plugin's tools through its `use_tool` tool, and the
  agent often names a tool without its server first. Grok checks the rules
  against the name as given, so the server-qualified rule alone does not match
  that first call. The second rule matches the short name; a write still
  matches neither.
- **Antigravity** asks before any MCP tool no rule allows, reads included, and
  names the plugin's server `cloudflare-tools_cloudflare-tools` (plugin,
  then server). `agy -p` refuses a tool it cannot ask about.
- **Hermes** `hermes chat -q` refuses a write it cannot ask about; `hermes -z`
  and `--yolo` skip approval, so writes then run unasked.

## Settings

Every entry point takes the same settings. A flag wins over its environment
variable, and an option or setting wins over the variable too.

| Setting         | Flag              | Variable                         | Meaning                                                                       |
| --------------- | ----------------- | -------------------------------- | ----------------------------------------------------------------------------- |
| `toolsets`      | `--toolsets`      | `CLOUDFLARE_TOOLS_TOOLSETS`      | the toolsets that are on, or `all`; default those `src/project.ts` names      |
| `read_only`     | `--read-only`     | `CLOUDFLARE_TOOLS_READ_ONLY`     | registers no tool that writes; wins over every other setting                  |
| `tools`         | `--tools`         | `CLOUDFLARE_TOOLS_TOOLS`         | single tools to add by ID, whatever their toolset                             |
| `exclude_tools` | `--exclude-tools` | `CLOUDFLARE_TOOLS_EXCLUDE_TOOLS` | single tools to remove by ID; wins over `tools` and `toolsets`                |
| `confirm`       | `--confirm`       | `CLOUDFLARE_TOOLS_CONFIRM`       | MCP server only: `harness` (default) or `elicitation`, as the conventions say |

Lists are comma-separated in a flag or a variable. Where each harness reads them:

- **OpenCode** takes the plugin `options` shown above, with lists as arrays and
  `read_only` as a boolean.
- **Hermes** takes `plugins.entries.cloudflare-tools.settings` in
  `config.yaml`:

  ```yaml
  plugins:
    entries:
      cloudflare-tools:
        settings:
          read_only: false
          exclude_tools: []
  ```

- **Claude Code and Antigravity** start the plugin's server without flags and
  pass it the client's environment, so set the variables in the environment the
  harness runs in.
- **Codex and Grok Build** pass no environment to a plugin's MCP server, so the
  variables have no effect there. To change settings in Codex, add the server
  under `[mcp_servers]` in Codex's config with flags in `args` (as in
  [Any other MCP host](#any-other-mcp-host)) instead of using the plugin.

### Credentials

The tools call Cloudflare with API tokens read from the environment, never
from a setting or an argument. Each token comes with the id of the one
account it acts on:

| Variables                                                       | Picked by                                    |
| --------------------------------------------------------------- | -------------------------------------------- |
| `CLOUDFLARE_API_TOKEN`, `CLOUDFLARE_ACCOUNT_ID`                 | `account` left out, or `account: "default"`  |
| `CLOUDFLARE_API_TOKEN_<LABEL>`, `CLOUDFLARE_ACCOUNT_ID_<LABEL>` | `account: "<label>"`, the label in lowercase |

- The unlabeled pair has the names Wrangler reads. A label is 1 to 24
  lowercase letters, digits and `_`, written in capitals in its variables
  (`my_site` → `CLOUDFLARE_API_TOKEN_MY_SITE`).
- With `account` left out, a call takes the unlabeled pair, or the only label
  when there is no unlabeled pair; with several labels and no unlabeled pair
  it fails and lists them.
- Use an API token scoped to what the tools should do; a Global API Key is
  refused.
- The variables reach the tools the way the settings do: in the environment
  OpenCode, Hermes, Claude Code and Antigravity run in, and in the `env` of an
  MCP host's server entry. Codex and Grok Build pass no environment to a
  plugin's server, so add the server to their own MCP configuration instead.

## Layout

- `src/project.ts` — the package's name, tool ID prefix, setting prefix and
  toolsets.
- `src/core/` — the tool contract and the tools, free of any harness and of
  the project's names.
- `src/tools.ts` — the list of every tool, which each entry point registers.
- `src/mcp/` — the MCP server, built to `dist/mcp.js` and run by
  `cloudflare-tools`.
- `src/opencode/` — the OpenCode plugin, built to `dist/opencode.js`.
- `src/cli/` — the command line the Hermes plugin calls, built to `dist/cli.js`
  and run by `cloudflare-tools-cli`.
- `src/hermes/`, `plugin.yaml` and `__init__.py` — the Hermes plugin.
- `skills/` — one skill per toolset, `skills/<name>/SKILL.md`, which every
  harness that takes skills loads from the package or plugin root.
- `plugin.json`, `mcp.json`, `.mcp.json`, `.claude-plugin/`, `.agents/plugins/`,
  `.antigravity-plugin/` and `mcp_config.json` — the plugin manifests of the
  other harnesses. The root `.mcp.json` is the configuration Grok Build reads
  (with `${CLAUDE_PLUGIN_ROOT}`, the variable both Grok Build and Claude Code
  expand); when working on this repository in Claude Code, decline the project
  MCP server it offers from that file.

How a release is made is in [`RELEASING.md`](RELEASING.md).

## License

[MIT](LICENSE)

TDQS

A4/5.0

Scored across 8 tools

Disambiguation4/5

Tools are largely distinct: cache_purge vs cache_purge_all are explicitly cross-referenced, and dnssec_enable/disable are clearly separated from the read-only dnssec status in zone_read. The only real risk is setting_edit vs setting_weaken, which requires the agent to know whether a change lowers protection — though setting_edit safely refuses such changes and points to weaken.

Naming Consistency5/5

Every tool follows the same cloudflare_<resource>_<action> snake_case pattern, with consistent prefixes for zone-scoped operations. Minor suffixes like _all and _enable/_disable fit the same convention without breaking it.

Tool Count5/5

Eight tools is well within the ideal range and each maps to a distinct, non-redundant operation on accounts or zones. Nothing feels padded or missing at the count level.

Completeness3/5

Zone settings, cache purge, and DNSSEC are covered read+write, but DNS record management (list/create/update/delete) — arguably the core of Cloudflare zone work — is absent, as is any zone creation/deletion. Agents will hit a dead end for common DNS-record tasks.

Maintenance

ActivityMaintained
ResponsivenessNo recent community issues