# Connect Cloudflare to Prodpeek Profile: `cloudflare/read-only` · connection kind: `cloudflare` · tier 2 Credential: A Cloudflare API Token, Read permissions only Suggested URL: `https://api.cloudflare.com/client/v4` Zones, DNS, WAF rules, zone settings, TLS, Tunnels, Workers, Pages and the account audit log — twenty-seven tools, one API token, nothing to install. Cloudflare is in front of production, so it is where a lot of outages are actually explained. ## What you must not do You cannot create this credential on the user's behalf — it needs their login and, usually, an approval step. Walk them through it and verify the result. Never ask them to paste the credential into the chat; it goes straight into Prodpeek's console, which encrypts it at rest. Do not suggest Cloudflare's MCP servers at *.mcp.cloudflare.com — they are OAuth-only and scoped to a signed-in human, so a gateway cannot use them. Prodpeek has a native Cloudflare adapter; the upstream is https://api.cloudflare.com/client/v4 and should not be changed. If the user offers a Global API Key, refuse it and ask for a scoped API Token. ## Grant exactly these permissions - `An API **Token** — never the Global API Key` — The Global API Key is account-wide, cannot be scoped, and cannot be made read-only. It is the exact credential this integration exists to avoid handing anyone. API Tokens are scoped per permission group, per zone and per account. - `Read permission groups only` — Zone Read, DNS Read, Zone Settings Read, Zone WAF Read, Workers Scripts Read, Cloudflare Tunnel Read, Access: Apps and Policies Read, Account Settings Read, Logs Read. Every one ends in Read, and that makes every write in this profile refused at Cloudflare as well as here. - `Zone Resources limited to the zones this connection should reach` — A token can be scoped to specific zones rather than all of them. If Prodpeek is reading one customer's domains, say so in the token — that fence is Cloudflare's and the gateway cannot widen it. - `A TTL, and an IP filter if you have a static egress address` — API Tokens support both. If your Prodpeek has a fixed outbound IP, an IP filter makes a stolen token useless from anywhere else. ## Refuse these, and say why if the user asks for them - `The Global API Key` — Full account access, every zone, every write, and it cannot be scoped or narrowed. If Prodpeek held one, the gateway would be the only fence in front of your entire Cloudflare account. - `Any Edit or Write permission group` — The profile denies every write and the adapter issues only GET, so it would change nothing about what Prodpeek does — but it removes the vendor's half of the fence, and that half is the one that still holds if this one has a bug. `Zone.Cache Purge` is the one people tick without thinking. - `Cloudflare Tunnel **Write**` — Write is what `/cfd_tunnel/{id}/token` requires, and that endpoint returns the token that RUNS the tunnel. Anyone holding it can stand up a connector into the network the tunnel reaches. Tunnel **Read** is what this profile needs. - `Account API Tokens Read` — It enumerates your other API tokens. A map of what else exists and what it can do. ## Verify before the credential is used - The token page says API Tokens, not Global API Key. - Every permission on the token ends in Read. - Zone Resources names specific zones rather than all zones, where you can. - Test connection shows list_dns_records under Allowed. - Calling `verify_token` returns status active. ## Then, in Prodpeek 1. Services → Add a service → choose the profile `cloudflare/read-only`. 2. Connection kind `cloudflare`, URL `https://api.cloudflare.com/client/v4`. 3. Paste the credential. It is encrypted in the store and never shown again. 4. Run **Test connection**. It lists every tool the upstream advertises and how the profile classifies each one. Anything under "not in the policy" is denied by default — report that list rather than assuming it is fine. ## Full human walkthrough ## The whole setup 1. Cloudflare dashboard → **My Profile → API Tokens → Create Token**. 2. **Create Custom Token**. Name it `prodpeek`. 3. Add **Read** permissions. A good starting set: | Scope | Permission | |---|---| | Account | Account Settings · **Read** | | Account | Workers Scripts · **Read** | | Account | Cloudflare Tunnel · **Read** | | Account | Access: Apps and Policies · **Read** | | Account | Logs · **Read** | | Zone | Zone · **Read** | | Zone | DNS · **Read** | | Zone | Zone Settings · **Read** | | Zone | Zone WAF · **Read** | 4. **Zone Resources**: pick the specific zones, not "All zones", if you can. 5. Set a **TTL**. Add an **IP filter** if your Prodpeek has a static egress address. 6. Create, and copy the token. 7. In Prodpeek: **Services → Add a service**, pick `cloudflare/read-only`, leave the upstream alone, paste the token, **Test connection**. !!! danger "Never the Global API Key" It is on the same page, it is easier to find, and it is account-wide with no scoping and no read-only mode. If you paste one here, the gateway becomes the only thing standing between an agent and your entire Cloudflare account. Prodpeek cannot tell the difference — only you can. ## Why there is no MCP server to install Cloudflare publishes MCP servers at `*.mcp.cloudflare.com`. They authenticate with OAuth 2.1 browser authorization scoped to the signed-in user — the same model as Grafana Cloud's, and unusable for the same reason: Prodpeek is a server-side gateway holding a stored team credential, with no browser and no signed-in user. Cloudflare's API v4 takes a scoped bearer token, which is exactly the shape Prodpeek is built around. So it speaks it directly. ## What your agent can then do ``` cf__list_zones which domains, and whether each is active cf__list_dns_records why it still resolves to the old origin cf__get_zone_settings SSL mode, minimum TLS, development mode left on cf__list_rulesets what Cloudflare is doing to requests cf__get_ruleset which WAF expression is blocking that client cf__list_tunnels whether the connector is actually up cf__list_workers_routes which paths a Worker handles instead of your origin cf__list_waiting_rooms a waiting room left enabled — an outage that looks like success cf__list_certificate_packs what expires, and when cf__list_audit_logs who changed what, just before this broke ``` `list_audit_logs` is the one nobody thinks to ask for and the one that most often ends the discussion. `get_zone_settings` is second: development mode left on, or SSL set to Flexible, explains a surprising number of "it works for me" reports. ## What it refuses, and the four that matter **`/cfd_tunnel/{id}/token` has no tool.** That endpoint returns the token that *runs* the tunnel. Anyone holding it can stand up a connector **into** the network that tunnel reaches — it is not a credential for reading Cloudflare, it is a credential for being inside your infrastructure. `list_tunnels` and `get_tunnel` exist because "is the connector up" is a real question, and the adapter strips `token` and `tunnel_secret` from those records on the way out. **Access service tokens have no tool.** They are how machines get past your Zero Trust policies. **Worker source has no tool.** `list_workers` gives names, sizes and modified dates. The script body is not a secret by design and is routinely one in practice — hardcoded API keys in Workers are common enough that this is not theoretical. **`purge_cache` has no tool, and it is the one people argue about.** It reads like housekeeping. It is a production change, and a global purge during an incident makes the incident worse: every request arrives at your origin at once, while the origin is already the thing in trouble. Pages env var **values** are stripped and the **keys** kept, on the same reasoning as everywhere else — "is `API_KEY` set at all" is the question. ## Why Tier 2 The Tier 1 half is real: a token built from Read permission groups refuses every write in this profile *at Cloudflare*, independently of the gateway. Keep it that way. It is Tier 2 anyway, because several allowed reads return infrastructure detail — origin IPs behind the proxy, WAF expressions, who changed what and from where — and Cloudflare has no permission group meaning "may list DNS records but not read their content". The roll-up decides the label. Cloudflare also offers no honest introspection: `/user/tokens/verify` says the token is live, not what it may do, and `/user/tokens` needs a permission a properly scoped token will not have. So this profile carries **no scope probe**, and [`prove`](../use/prove.md) reports the scope as *not introspectable* rather than claiming a check it never made. ## Checking the token really cannot write ```bash CF=https://api.cloudflare.com/client/v4 # Should succeed, and say "active" curl -sS -H "Authorization: Bearer $CF_TOKEN" "$CF/user/tokens/verify" | head -c 200 # Should FAIL: a cache purge is the cheapest reversible write to attempt ZONE=your_zone_id curl -sS -o /dev/null -w '%{http_code}\n' -X POST \ -H "Authorization: Bearer $CF_TOKEN" -H 'Content-Type: application/json' \ -d '{"purge_everything":false,"files":["https://example.com/prodpeek-permission-test"]}' \ "$CF/zones/$ZONE/purge_cache" ``` A `403` on the second is the vendor fence doing its half. A `200` means the token holds `Zone.Cache Purge` — remake it without that permission group.