src/tools/cloudflare.ts
37 lines and 3 functions in DrDustinEdwards/capsid, at commit daa875b on 2026-10-24. It is in an import cycle. This file on GitHub.
Functions
| Function | Lines | Complexity | Covered | CRAP | Mutation |
|---|---|---|---|---|---|
| defineCloudflareOperations | 10 to 40 | 1 | 100% | OkCRAP 1 | No data: mutation testing has not run on this repo yet |
| <anonymous:30> | 30 to 38 | 5 | 100% | OkCRAP 5 | No data: mutation testing has not run on this repo yet |
| <anonymous:34> | 34 to 34 | 1 | 100% | OkCRAP 1 | No data: mutation testing has not run on this repo yet |
Imports
It imports src/limits.ts, src/ops-cloudflare-config.ts, src/tools/docs.ts. Imported by src/server.ts.
Source
import { McpServer } from "@modelcontextprotocol/server";
import { z } from "zod";
import { CF_CONFIG_ACTIONS, CF_PERMISSION, readCloudflareConfig } from "../ops-cloudflare-config";
import { fail, ok, type ToolCtx } from "./docs";
import { bounded } from "../limits";
// A zone name as Cloudflare lists it: labels of letters, digits and hyphens.
const ZONE_NAME = /^(?=.{1,253}$)([a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,63}$/i;
export function defineCloudflareOperations(server: McpServer, ctx: ToolCtx): void {
const { env } = ctx;
// Cloudflare Access and Email Routing, read. Ruled 2026-09-29, "Capsid MCP roadmap,
// build or don't build". Admin only in whole: it reads the
// account's login gates and mail routes, which span every namespace. TOOL_GRANTS in
// src/scope.ts states it and the registrar refuses anyone else before this handler
// runs. It reads only, through src/ops-cloudflare-config.ts, which copies named
// fields and never passes a Cloudflare object through, so no client secret, SCIM
// credential or aud tag reaches the caller.
ctx.ops.define(
"cloudflare_config",
{
description:
`Cloudflare Access and Email Routing configuration for the account, read with CF_OPS_TOKEN. Admin only. It reads and never writes. action "access_apps": each Access application's id, name, domain, type, self_hosted_domains, destinations (type, uri, hostname) and its policies (name, decision). action "access_policies": each reusable policy's id, name, decision and its include, exclude and require rules as types, with the value only for email (the address), email_domain (the domain), group (the group id) and service_token (the token id). action "email_rules": each zone's Email Routing rules (name, enabled, priority, matchers, actions); zone narrows it to one zone name, else every zone in the account. action "email_addresses": each destination address and whether it is verified. Client secrets, SCIM credentials and aud tags are never returned. A 403 names the permission the token lacks: ${CF_PERMISSION.access} for Access, ${CF_PERMISSION.zones} and ${CF_PERMISSION.rules} for rules, ${CF_PERMISSION.addresses} for addresses.`,
inputSchema: {
action: z.enum(CF_CONFIG_ACTIONS).describe("access_apps | access_policies | email_rules | email_addresses."),
zone: bounded(253).regex(ZONE_NAME).optional().describe("For email_rules: one zone name, such as example.com. Omitted reads every zone in the account."),
},
},
async (args) => {
try {
if (args.zone !== undefined && args.action !== "email_rules") return fail(`zone applies to email_rules only, and this call is ${args.action}.`);
// Looked up per call, not captured, so the Worker's fetch is the one in scope.
return ok(await readCloudflareConfig(env, args.action, args.zone?.toLowerCase(), (url, init) => fetch(url, init)));
} catch (err) {
return fail(err instanceof Error ? err.message : String(err));
}
}
);
}