Cloudflare Ops MCP - Unofficial Cloudflare DNS, Email, Pages, Cache, Turnstile & MCP Toolkit for AI Agents
An independent Cloudflare Ops, DNS, DMARC, BIMI, SPF, Email Routing, Pages, cache, Turnstile, and Wrangler-friendly MCP toolkit for developers, operators, and AI agents.
Cloudflare Ops MCP scans Cloudflare configuration, computes a diff of desired vs current DNS / Email Routing / BIMI / DMARC / SPF / Pages / cache / Turnstile setup, and applies fixes only after explicit approval. It is built for people who want an AI agent to help with Cloudflare safely: scan first, show the plan, then write only when the owner approves.
It works three ways:
- CLI: run
cfopslocally with a scoped Cloudflare token. - Library: import the zero-dependency engine into your own app.
- Remote MCP Worker: deploy the included Worker with Wrangler so Claude, Codex, Cursor, or another MCP client can call Cloudflare DNS tools through a locked endpoint.
Cloudflare Ops MCP is especially useful for Cloudflare operators who need repeatable DNS hygiene across many zones: SPF cleanup, DMARC enforcement, BIMI records, MX checks, DKIM discovery, Cloudflare Email Routing, TXT verification records, safe DNS upserts, and audit logs for every approved write.
Unofficial Cloudflare tool. Cloudflare Ops MCP is made by AMH - Artificial Mind Hive, operated by Service Pricer LLC. It is independent, third-party, open-source software. It is not affiliated with, endorsed by, sponsored by, or made by Cloudflare, Inc. "Cloudflare" and "Wrangler" are referenced only to describe compatibility with Cloudflare's platform and official developer tooling. You are responsible for every DNS, Email Routing, DMARC, BIMI, SPF, or Worker change you approve and apply.
Cloudflare Ops MCP is focused on the Cloudflare tasks that regularly break launches, email trust, bot checks, cache freshness, brand display, and AI-agent workflows:
- DNS record scan, lookup, create, update, no-op detection, and guarded delete.
- SPF detection and planning so you do not accidentally create multiple SPF records.
- DMARC parsing and policy updates that only change the fields you asked for.
- BIMI TXT setup with a DMARC enforcement gate.
- DKIM discovery so the report can tell whether sender keys exist.
- MX and Cloudflare Email Routing checks.
- Email Routing destination/rule setup with verified-destination protection.
- Audit logs for applied changes.
- Cloudflare Pages DNS cutovers and cache purge operations.
- Turnstile widget planning/creation for bot checks.
- MCP endpoint deployment on Cloudflare Workers with Wrangler secrets.
- AI-agent safety defaults: dry-run first, diff display, no token in tool args, no accidental deletes.
This is not meant to replace every Cloudflare feature. It is the narrow, safe lane for common Cloudflare ops an operator or AI agent should be allowed to do.
The examples below are intentionally direct. They are not toy examples. They show the exact dry-run -> review -> apply pattern users should follow when fixing real domains.
Use this when an old registrar, parking page, Vercel app, HugeDomains page, or stale A/AAAA records are blocking a Cloudflare Pages custom domain.
Dry-run first:
cfops pages example.com --target project.pages.devApply only after reviewing the delete/create plan:
cfops pages example.com --target project.pages.dev --applyWhat it changes:
- Deletes conflicting apex A/AAAA/CNAME records.
- Deletes conflicting www A/AAAA/CNAME records and www NS delegations.
- Adds proxied CNAME records for apex and www pointing at the Pages target.
- Leaves TXT, MX, SPF, DKIM, DMARC, BIMI, and other delegated subdomains alone.
- Leaves wildcard records alone unless you explicitly add
--wildcard.
Options:
--no-wwwonly cuts over the apex domain.--wildcardalso removes conflicting*.example.comA/AAAA/CNAME records.
Wrangler is Cloudflare's official developer CLI. Cloudflare Ops MCP can run without Wrangler as a local CLI/library, but Wrangler is the right path when you want a remote MCP server because it deploys the Worker, stores secrets, tails logs, and manages Cloudflare bindings from the same toolchain Cloudflare documents.
Use Wrangler when you want:
- a hosted MCP endpoint for agents and teammates;
- Worker secrets for
CLOUDFLARE_API_TOKENandMCP_ACCESS_KEY; - Cloudflare Pages/Workers deployment checks;
- observability through Cloudflare logs;
- a repeatable production setup instead of local-only scripts.
|
Agent-safe Cloudflare writes Cloudflare Ops MCP keeps the write path narrow: scan the target, show the diff, wait for explicit approval, then apply only the requested DNS, DMARC, BIMI, SPF, Email Routing, Pages, cache, or Turnstile change. |
|
Worker MCP endpoint hardening The hosted MCP lane is built for Cloudflare Workers and Wrangler. The Cloudflare API token stays in Worker secrets, the public endpoint requires an MCP access key, and mutating tools stay dry-run unless |
|
Email trust diagnostics The scanner reports SPF, DKIM discovery, DMARC policy, BIMI readiness, MX records, and Cloudflare Email Routing status so an agent or operator can see what is missing before touching production DNS. |
|
Other highlights
|
- Dry-run by default. Every mutating function takes an options object and
only writes when
{ apply: true }is passed. The CLI is dry-run unless you add--apply. A dry-run returns the planned change plus a before/after diff and writes nothing. - Scan before write. Apply paths re-fetch current state and return a before/after diff, so you always see exactly what will change.
- Never delete as a side effect. Deleting a DNS record requires an explicit
deleteDnsRecord(..., { confirm: true })call (CLI:--force). Anapplynever deletes anything. - Token hygiene. The Cloudflare API token is read only from
process.env.CLOUDFLARE_API_TOKEN. It is never logged, never written to the audit log, and never included in thrown error messages - any token-looking substring is redacted defensively. - Scoped token only. Use a least-privilege scoped API token. Do not use the Global API Key.
- BIMI precondition.
setupBimifirst checks the domain's DMARC policy. Ifpis missing ornone, it refuses to write in apply mode (BIMI is not honored below enforcement) unless you pass{ force: true }. In dry-run it warns. - Audit log. Every apply appends one JSON line to an audit log
(default
./cloudflare-ops-mcp-audit.log) with{ ts, action, domain, record, before, after }- never the token.
For the fastest path, start with SETUP.md. It explains the CLI path, the Wrangler-hosted MCP path, and how users provide their own Cloudflare credentials safely. See PHASES.md for the rollout model, OAUTH.md for hosted Cloudflare OAuth, and ROADMAP.md for the next upgrades.
npm install cloudflare-ops-mcp
# or run the CLI without installing:
npx cloudflare-ops-mcp scan example.comRequires Node.js >= 18 (for the built-in global fetch). No other
dependencies.
- Cloudflare dashboard -> My Profile -> API Tokens -> Create Token.
- Choose Create Custom Token.
- Under Permissions, add exactly these three:
- Zone -> Zone -> Read
- Zone -> DNS -> Edit
- Zone -> Email Routing Rules -> Edit
- Under Zone Resources, select Include -> Specific zone -> your domain (not "All zones").
- (Recommended) set a TTL and/or Client IP Address Filtering.
- Continue -> Create Token, then copy the token value once (it is shown only at creation; if lost, roll it).
Do not use the Global API Key - it has access to everything, cannot be scoped or time-limited, and there is only one per account.
Put it in your environment (never in a CLI argument or in code):
export CLOUDFLARE_API_TOKEN="your-scoped-token"or copy .env.example to .env and fill it in:
cp .env.example .env
# then load it however you prefer (e.g. `set -a; . ./.env; set +a`)cloudflare-ops-mcp reads CLOUDFLARE_API_TOKEN from the environment only.
For a hosted app, use Cloudflare OAuth instead of a shared permanent API token. Git ships the OAuth connector, but each deployment stores its own OAuth client ID/secret as Worker secrets and each user connects Cloudflare through consent.
npm run oauth:setup -- https://<your-worker-host>
cd worker
npx wrangler secret put CLOUDFLARE_OAUTH_CLIENT_ID
npx wrangler secret put CLOUDFLARE_OAUTH_CLIENT_SECRET
npx wrangler secret put CLOUDFLARE_OAUTH_REDIRECT_URI
npx wrangler deployThen send the user to:
https://<your-worker-host>/oauth/cloudflare/start?tenant=<user-or-account-id>MCP tools can receive { "tenant": "<user-or-account-id>" }. The Worker uses that tenant's OAuth token server-side and never exposes it to the agent. See OAUTH.md.
Cloudflare Ops MCP does not ship with an API key, shared account, hosted proxy token, or any hidden credentials. Every operator must bring one of these:
- a scoped Cloudflare API token in their own environment for local CLI/library use;
- a scoped Cloudflare API token stored as their own Worker secret for self-hosted MCP use;
- a per-user Cloudflare OAuth connection stored server-side by the Worker for hosted app use;
- an OAuth/token-vault integration built by their own host app, with approval gates before writes.
Do not ask users to send tokens through chat, issues, logs, or screenshots. If a token is exposed, rotate it in Cloudflare and create a new scoped token.
cfops <command> <domain> [options]
| Command | What it does |
|---|---|
scan <domain> |
Full read-only snapshot: DNS, SPF, DKIM, DMARC, BIMI, Email Routing. |
plan <domain> [--inbox x@y] |
Report desired vs current email-auth posture (no writes). |
dns <domain> --type T --name N --content C [--ttl n] [--proxied] [--apply] |
Upsert a DNS record (create / update / no-op). |
email <domain> --forward a@b=to@c [--catch-all to@c] [--apply] |
Plan/apply Email Routing forward rules + catch-all. |
dmarc <domain> --policy quarantine [--rua mailto:x] [--pct 25] [--apply] |
Change only the DMARC p= (and rua/pct), safely. |
bimi <domain> --logo <url> [--vmc <url>] [--apply] [--force] |
Set default._bimi TXT (refuses if DMARC=none unless --force). |
verify <domain> |
Verify the API token, then resolve the zone. |
Global flags: --apply (perform the write; default is dry-run), --force
(BIMI DMARC override / delete), --audit <path> (audit log location),
-h/--help.
Everything is a dry-run until you add --apply. BIMI needs a hosted
SVG Tiny-PS logo URL that you supply via --logo (and, for Gmail/Apple Mail
display, a VMC/CMC via --vmc).
Assume a scoped token is exported and each domain is a zone in your account.
First, dry-run (writes nothing - shows the diff):
cfops bimi example.com --logo https://example.com/bimi/logo.svgBIMI for example.com (DMARC p=quarantine, enforcing=true):
new record: v=BIMI1; l=https://example.com/bimi/logo.svg
action: create (dry-run)
record: TXT default._bimi.example.com
- before: (record does not exist)
+ after: content="v=BIMI1; l=https://example.com/bimi/logo.svg" ttl=1
warn: No VMC/CMC supplied (a=). Gmail and Apple Mail require a VMC/CMC to display the logo; Yahoo/AOL do not.
Dry-run only. Re-run with --apply to write this change.
Then apply:
cfops bimi example.com --logo https://example.com/bimi/logo.svg --applyIf
example.com's DMARC were still atp=none, the apply would be blocked with an error telling you to raise DMARC first (or pass--force). Fix DMARC, then set BIMI.
Dry-run:
cfops bimi example.org --logo https://example.org/bimi/logo.svgApply once the diff looks right:
cfops bimi example.org --logo https://example.org/bimi/logo.svg --apply(For broad display in Gmail/Apple Mail, host a VMC/CMC and add
--vmc https://example.org/bimi/vmc.pem.)
Dry-run first - note it changes only p, preserving your existing rua:
cfops dmarc example.org --policy quarantine --rua mailto:dmarc@example.org --pct 25DMARC none -> quarantine for example.org:
new record: v=DMARC1; p=quarantine; rua=mailto:dmarc@example.org; pct=25
action: update (dry-run)
record: TXT _dmarc.example.org
- before: content="\"v=DMARC1; p=none; rua=mailto:dmarc@example.org\"" ttl=1
+ after: content="\"v=DMARC1; p=quarantine; rua=mailto:dmarc@example.org; pct=25\"" ttl=1
changed fields: content
Dry-run only. Re-run with --apply to write this change.
Then apply:
cfops dmarc example.org --policy quarantine --rua mailto:dmarc@example.org --pct 25 --applyRamp safely. Only flip to
quarantineafterp=none+ruareports show all your legitimate mail is authenticating with alignment. Then widen--pct 25 -> 50 -> 100over a couple of weeks before ever consideringp=reject.pctis honored today but is being removed in the in-progress DMARCbis revision - treat it as current best practice, not forever.
cloudflare-ops-mcp exposes clean named exports so a host app can import and wrap it
(add your own auth, approval UI, or multi-tenant token vault) - but it has
no dependency on any host and runs perfectly standalone.
import {
CloudflareClient,
scanZone,
applyDnsRecord,
setDmarcPolicy,
setupBimi,
setupEmailRouting,
planEmailAuth,
appendAudit,
} from "cloudflare-ops-mcp";
const client = new CloudflareClient(); // reads CLOUDFLARE_API_TOKEN from env
// (you may also inject { token, fetch } - useful for tests)
// Read-only snapshot:
const snapshot = await scanZone(client, "example.com");
// Plan a DMARC flip (dry-run - writes nothing):
const plan = await setDmarcPolicy(client, "example.com", "quarantine", {
rua: "mailto:dmarc@example.com",
pct: 25,
});
// Apply it, then record the change yourself (you supply the timestamp):
const applied = await setDmarcPolicy(
client,
"example.com",
"quarantine",
{ rua: "mailto:dmarc@example.com", pct: 25 },
{ apply: true }
);
appendAudit("./cloudflare-ops-mcp-audit.log", {
ts: new Date().toISOString(),
action: "dmarc.policy",
domain: "example.com",
record: applied.record,
before: applied.before,
after: applied.after,
});Every mutating export is dry-run unless you pass { apply: true }, and pure
logic never calls Date.now() - you (or the CLI) supply audit timestamps.
CloudflareClient,CloudflareError,redactTokenresolveZoneId,resolveZone,scanZonelistRecords,findRecord,applyDnsRecord,deleteDnsRecordgetRoutingStatus,enableRouting,listDestinations,addDestination,listRules,getCatchAll,setupEmailRoutingparseDmarc,buildDmarc,parseSpf,getDmarc,setDmarcPolicy,quoteTxt,unquoteTxtparseBimi,buildBimi,validateBimiSvgUrl,setupBimiplanEmailAuthappendAudit,AUDIT_DEFAULT_PATH
cloudflare-ops-mcp is a generic, open tool. A larger platform can import it to
automate zone hygiene for its users (wrapping it with per-account tokens and an
approval step), but it is completely standalone - the library and CLI run on
their own with nothing but a scoped Cloudflare token.
worker/ is an optional Model Context Protocol
server (a Cloudflare Worker) that exposes the same engine as tools over a URL, so
any MCP client - an agent, Claude, Cursor - can scan_zone, plan_email_auth,
set_dmarc_policy, setup_bimi, etc. by pointing at it.
Three deliberate hardening choices:
- The token is a Worker secret, never a tool parameter - so it never travels through an MCP client's logs or an agent's transcript.
- The endpoint itself is locked. Because these tools can mutate DNS, a public
Worker URL must not be callable by anyone who finds it. Set
MCP_ACCESS_KEYand the server rejects any request withoutAuthorization: Bearer <MCP_ACCESS_KEY>(or anX-MCP-Keyheader). Deploying without it makes POST requests fail closed with503, so DNS tools are never accidentally exposed. - Every mutating tool is dry-run by default; the caller must pass
apply: trueafter seeing the diff. BIMI keeps its DMARC precondition.
npm run worker:generate-key # creates a strong MCP_ACCESS_KEY
cd worker
npx wrangler secret put CLOUDFLARE_API_TOKEN # optional fallback scoped CF token; OAuth is preferred for hosted user apps
npx wrangler secret put MCP_ACCESS_KEY # paste generated zm_ key
npx wrangler deployPoint your MCP client at the deployed URL and add the header
Authorization: Bearer <MCP_ACCESS_KEY>.
The core library has zero dependencies; the Worker is an optional surface - you never need it to use the CLI.
npm test # node --test (mock fetch, no live network calls)Made by AMH - Artificial Mind Hive, operated by Service Pricer LLC.
- Service Pricer: https://servicepricer.app
- GitHub: https://github.com/pain2hustle/cloudflare-ops-mcp
MIT (c) 2026 Pain2HuStle