@zackbart/connecta 0.28.1 → 0.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (332) hide show
  1. package/CHANGELOG.md +855 -5
  2. package/README.md +125 -116
  3. package/bin/connecta.mjs +121 -139
  4. package/bin/meta-tool-names.d.mts +2 -0
  5. package/bin/meta-tool-names.mjs +9 -0
  6. package/bin/version.mjs +3 -0
  7. package/dist/access-tokens.js +53 -63
  8. package/dist/activity-request.js +8 -0
  9. package/dist/activity.d.ts +76 -42
  10. package/dist/activity.js +110 -42
  11. package/dist/argument-redaction.js +230 -0
  12. package/dist/auth/clerk-sdk/client.d.ts +65 -0
  13. package/dist/auth/clerk-sdk/client.js +5 -0
  14. package/dist/auth/clerk-sdk/generated.js +1845 -0
  15. package/dist/auth/clerk-transport.js +19 -0
  16. package/dist/auth/clerk.d.ts +17 -7
  17. package/dist/auth/clerk.js +189 -107
  18. package/dist/auth/cloudflare-access.js +25 -26
  19. package/dist/auth/downstream-client-metadata.d.ts +29 -0
  20. package/dist/auth/downstream-client-metadata.js +133 -0
  21. package/dist/auth/downstream-oauth.js +1584 -1580
  22. package/dist/auth/downstream-revocation.js +106 -0
  23. package/dist/auth/oauth-token-response.js +231 -0
  24. package/dist/auth/oauth-v2-migration.js +116 -0
  25. package/dist/auth/static-oauth.js +93 -71
  26. package/dist/auth-elicitation.js +288 -0
  27. package/dist/authorization-handoff.js +111 -0
  28. package/dist/branding.d.ts +8 -16
  29. package/dist/branding.js +26 -59
  30. package/dist/byte-read-response.js +39 -0
  31. package/dist/call-admission.js +12 -16
  32. package/dist/catalog-cache.js +739 -0
  33. package/dist/catalog-drift.d.ts +50 -15
  34. package/dist/catalog-drift.js +365 -128
  35. package/dist/catalog-fingerprint.js +2 -4
  36. package/dist/catalog-freshness.js +13 -0
  37. package/dist/catalog-limits.js +2 -2
  38. package/dist/catalog-service.js +409 -225
  39. package/dist/catalog.js +43 -84
  40. package/dist/concurrency.js +3 -4
  41. package/dist/config-defaults.d.ts +39 -0
  42. package/dist/config-defaults.js +46 -0
  43. package/dist/config-schema.d.ts +181 -0
  44. package/dist/config-schema.js +435 -0
  45. package/dist/config-value-sources.js +102 -0
  46. package/dist/config.d.ts +633 -0
  47. package/dist/config.js +508 -0
  48. package/dist/connector-access.d.ts +1 -0
  49. package/dist/connector-access.js +13 -15
  50. package/dist/connector-caller.d.ts +8 -0
  51. package/dist/connector-caller.js +15 -6
  52. package/dist/connector-scope.js +1 -1
  53. package/dist/connectors/api-connector.d.ts +59 -7
  54. package/dist/connectors/api-connector.js +106 -19
  55. package/dist/connectors/api.js +4 -1
  56. package/dist/connectors/guarded-fetch.d.ts +144 -0
  57. package/dist/connectors/guarded-fetch.js +87 -38
  58. package/dist/connectors/negotiation-cache.js +46 -0
  59. package/dist/connectors/option-shapes.js +27 -0
  60. package/dist/connectors/remote-mcp.d.ts +30 -4
  61. package/dist/connectors/remote-mcp.js +1774 -320
  62. package/dist/connectors/resource-uri.js +335 -0
  63. package/dist/credential-contract.d.ts +10 -0
  64. package/dist/credential-rules.js +1 -3
  65. package/dist/credential-url.js +108 -0
  66. package/dist/credentials.d.ts +6 -1
  67. package/dist/credentials.js +27 -18
  68. package/dist/d1.d.ts +130 -0
  69. package/dist/d1.js +290 -0
  70. package/dist/describe-config.d.ts +165 -0
  71. package/dist/describe-config.js +315 -0
  72. package/dist/described.js +106 -0
  73. package/dist/downstream-input-context.js +128 -0
  74. package/dist/downstream-input.js +325 -0
  75. package/dist/errors.d.ts +132 -31
  76. package/dist/errors.js +175 -87
  77. package/dist/execute.js +798 -318
  78. package/dist/executor-admission.d.ts +1 -0
  79. package/dist/executor-admission.js +11 -15
  80. package/dist/executor-contract.d.ts +20 -0
  81. package/dist/executor-contract.js +48 -0
  82. package/dist/executor-result.js +30 -13
  83. package/dist/executors/quickjs-child.js +15 -9
  84. package/dist/executors/quickjs-protocol.js +5 -6
  85. package/dist/executors/quickjs-runtime.js +152 -59
  86. package/dist/executors/quickjs.js +49 -39
  87. package/dist/favicon.js +1 -2
  88. package/dist/fix-prompt.js +1 -3
  89. package/dist/guest-runtime.js +238 -0
  90. package/dist/guest-types.d.ts +218 -0
  91. package/dist/guest-types.js +1 -0
  92. package/dist/html-security.js +24 -0
  93. package/dist/identity.js +1 -3
  94. package/dist/inbound-credential.js +52 -0
  95. package/dist/index.d.ts +24 -256
  96. package/dist/index.js +65 -478
  97. package/dist/invocation-auth.js +69 -0
  98. package/dist/invocation.js +262 -79
  99. package/dist/json-schema.d.ts +1 -1
  100. package/dist/mcp-client-context.js +35 -0
  101. package/dist/mcp-result.js +11 -7
  102. package/dist/meta-output.js +61 -0
  103. package/dist/meta-tool-names.d.ts +2 -0
  104. package/dist/meta-tool-names.js +3 -0
  105. package/dist/meta-tools.js +419 -272
  106. package/dist/module-contracts.d.ts +8 -23
  107. package/dist/node.d.ts +0 -2
  108. package/dist/node.js +2 -2
  109. package/dist/oauth-callback-outcome.js +1 -3
  110. package/dist/oauth-handoff.js +194 -0
  111. package/dist/oauth-sealing.js +20 -0
  112. package/dist/operator-record.d.ts +137 -0
  113. package/dist/operator-record.js +420 -0
  114. package/dist/operator-ui/assets.js +31 -0
  115. package/dist/operator-ui/contract.d.ts +72 -0
  116. package/dist/operator-ui/contract.js +1 -0
  117. package/dist/operator-ui/fix-prompts.d.ts +18 -0
  118. package/dist/operator-ui/generated.js +5 -4
  119. package/dist/operator-ui/model.d.ts +12 -87
  120. package/dist/operator-ui/model.js +2 -9
  121. package/dist/operator-ui/setup-commands.js +1 -3
  122. package/dist/operator-ui/view.d.ts +323 -0
  123. package/dist/operator-ui/view.js +62 -189
  124. package/dist/page-styles.js +2 -3
  125. package/dist/program-result.js +5 -0
  126. package/dist/program-source.js +14 -2
  127. package/dist/{exempt-writes.js → program-writes.js} +60 -19
  128. package/dist/provider.d.ts +142 -0
  129. package/dist/provider.js +187 -0
  130. package/dist/providers/_shared/google/result-size.js +57 -0
  131. package/dist/providers/_shared/google/service-account.d.ts +58 -0
  132. package/dist/providers/_shared/google/service-account.js +500 -0
  133. package/dist/providers/_shared/google/workspace.d.ts +276 -0
  134. package/dist/providers/_shared/google/workspace.js +518 -0
  135. package/dist/providers/_shared/request-token.js +83 -0
  136. package/dist/providers/{basecamp.d.ts → basecamp/index.d.ts} +6 -28
  137. package/dist/providers/basecamp/index.js +997 -0
  138. package/dist/providers/basecamp/skill.generated.js +9 -0
  139. package/dist/providers/{breeze.d.ts → breeze/index.d.ts} +2 -2
  140. package/dist/providers/{breeze.js → breeze/index.js} +184 -140
  141. package/dist/providers/breeze/skill.generated.js +9 -0
  142. package/dist/providers/{ccb.d.ts → ccb/index.d.ts} +2 -2
  143. package/dist/providers/{ccb.js → ccb/index.js} +271 -116
  144. package/dist/providers/ccb/skill.generated.js +9 -0
  145. package/dist/providers/{cloudflare.d.ts → cloudflare/index.d.ts} +8 -7
  146. package/dist/providers/{cloudflare.js → cloudflare/index.js} +171 -738
  147. package/dist/providers/cloudflare/skill.generated.js +10 -0
  148. package/dist/providers/docs/index.d.ts +54 -0
  149. package/dist/providers/docs/index.js +1237 -0
  150. package/dist/providers/docs/skill.generated.js +9 -0
  151. package/dist/providers/drive/index.d.ts +53 -0
  152. package/dist/providers/drive/index.js +1523 -0
  153. package/dist/providers/drive/skill.generated.js +9 -0
  154. package/dist/providers/forms/index.d.ts +54 -0
  155. package/dist/providers/forms/index.js +1217 -0
  156. package/dist/providers/forms/skill.generated.js +11 -0
  157. package/dist/providers/github/auth.d.ts +32 -0
  158. package/dist/providers/github/auth.js +361 -0
  159. package/dist/providers/github/hosted.js +218 -0
  160. package/dist/providers/github/index.d.ts +13 -0
  161. package/dist/providers/github/index.js +140 -0
  162. package/dist/providers/github/key.js +100 -0
  163. package/dist/providers/github/rest.js +321 -0
  164. package/dist/providers/github/scope.d.ts +29 -0
  165. package/dist/providers/github/scope.js +124 -0
  166. package/dist/providers/github/skill.generated.js +8 -0
  167. package/dist/providers/gmail/index.d.ts +51 -0
  168. package/dist/providers/gmail/index.js +1436 -0
  169. package/dist/providers/gmail/skill.generated.js +9 -0
  170. package/dist/providers/infisical/index.d.ts +7 -0
  171. package/dist/providers/infisical/index.js +713 -0
  172. package/dist/providers/infisical/skill.generated.js +8 -0
  173. package/dist/providers/{linear.d.ts → linear/index.d.ts} +11 -14
  174. package/dist/providers/linear/index.js +352 -0
  175. package/dist/providers/linear/skill.generated.js +8 -0
  176. package/dist/providers/{mixpanel.d.ts → mixpanel/index.d.ts} +5 -10
  177. package/dist/providers/mixpanel/index.js +416 -0
  178. package/dist/providers/mixpanel/skill.generated.js +9 -0
  179. package/dist/providers/{notion.d.ts → notion/index.d.ts} +8 -8
  180. package/dist/providers/{notion.js → notion/index.js} +226 -239
  181. package/dist/providers/notion/skill.generated.js +10 -0
  182. package/dist/providers/{overflow.d.ts → overflow/index.d.ts} +2 -2
  183. package/dist/providers/{overflow.js → overflow/index.js} +74 -111
  184. package/dist/providers/overflow/skill.generated.js +8 -0
  185. package/dist/providers/{planning-center.d.ts → planning-center/index.d.ts} +2 -2
  186. package/dist/providers/{planning-center.js → planning-center/index.js} +264 -104
  187. package/dist/providers/planning-center/skill.generated.js +10 -0
  188. package/dist/providers/{revenuecat.d.ts → revenuecat/index.d.ts} +5 -10
  189. package/dist/providers/revenuecat/index.js +557 -0
  190. package/dist/providers/revenuecat/skill.generated.js +11 -0
  191. package/dist/providers/sheets/index.d.ts +54 -0
  192. package/dist/providers/sheets/index.js +1237 -0
  193. package/dist/providers/sheets/skill.generated.js +10 -0
  194. package/dist/providers/slides/index.d.ts +53 -0
  195. package/dist/providers/slides/index.js +2645 -0
  196. package/dist/providers/slides/skill.generated.js +9 -0
  197. package/dist/providers/{stripe.d.ts → stripe/index.d.ts} +5 -12
  198. package/dist/providers/stripe/index.js +266 -0
  199. package/dist/providers/stripe/skill.generated.js +11 -0
  200. package/dist/providers/{tithely.d.ts → tithely/index.d.ts} +2 -2
  201. package/dist/providers/{tithely.js → tithely/index.js} +60 -91
  202. package/dist/providers/tithely/skill.generated.js +8 -0
  203. package/dist/providers/{vercel.d.ts → vercel/index.d.ts} +8 -8
  204. package/dist/providers/vercel/index.js +1038 -0
  205. package/dist/providers/vercel/mcp-ownership.js +212 -0
  206. package/dist/providers/vercel/skill.generated.js +11 -0
  207. package/dist/registry.d.ts +110 -171
  208. package/dist/registry.js +588 -927
  209. package/dist/request-state.js +21 -0
  210. package/dist/resource-read.js +103 -0
  211. package/dist/result-shapes.js +17 -49
  212. package/dist/retired-toolkits.js +1 -1
  213. package/dist/routes/access-tokens.js +3 -6
  214. package/dist/routes/activity-context.js +21 -0
  215. package/dist/routes/activity-disclosure.js +40 -0
  216. package/dist/routes/activity.js +59 -21
  217. package/dist/routes/actor-labels.js +3 -50
  218. package/dist/routes/connect.js +221 -0
  219. package/dist/routes/credentials.js +15 -27
  220. package/dist/routes/mcp.js +365 -130
  221. package/dist/routes/oauth-client-metadata.js +15 -0
  222. package/dist/routes/oauth-management.js +45 -107
  223. package/dist/routes/oauth.js +110 -57
  224. package/dist/routes/operator.js +3 -3
  225. package/dist/routes/shared.d.ts +25 -64
  226. package/dist/routes/shared.js +160 -80
  227. package/dist/routes/skills.js +30 -0
  228. package/dist/routes/ui-config.js +225 -0
  229. package/dist/routes/ui-data.js +40 -35
  230. package/dist/routes/ui.js +66 -56
  231. package/dist/runtime/admission.js +4 -2
  232. package/dist/runtime/call-admission.js +3 -1
  233. package/dist/runtime/connector-scope.js +1 -4
  234. package/dist/runtime/run.js +10 -39
  235. package/dist/runtime/services.js +12 -117
  236. package/dist/runtime/shared-read.js +1 -3
  237. package/dist/runtime/storage.js +8 -1
  238. package/dist/sent-secrets.d.ts +93 -0
  239. package/dist/sent-secrets.js +1013 -0
  240. package/dist/server.js +28 -71
  241. package/dist/skills.js +416 -174
  242. package/dist/sqlite.d.ts +48 -0
  243. package/dist/sqlite.js +181 -0
  244. package/dist/storage/keys.js +443 -0
  245. package/dist/storage/memory.js +16 -4
  246. package/dist/storage/sql.d.ts +66 -0
  247. package/dist/storage/sql.js +543 -0
  248. package/dist/timeout.js +4 -2
  249. package/dist/tool-name.js +17 -0
  250. package/dist/tool-safety.d.ts +9 -35
  251. package/dist/tool-safety.js +20 -40
  252. package/dist/types.d.ts +336 -56
  253. package/dist/types.js +0 -1
  254. package/dist/ui.d.ts +7 -19
  255. package/dist/ui.js +76 -90
  256. package/dist/url-safety.js +3 -6
  257. package/dist/usage-guide.js +146 -0
  258. package/dist/validate.d.ts +7 -0
  259. package/dist/validate.js +309 -47
  260. package/dist/version.d.ts +1 -7
  261. package/dist/version.js +3 -7
  262. package/dist/worker.d.ts +11 -0
  263. package/dist/worker.js +435 -0
  264. package/documentation/README.md +63 -0
  265. package/documentation/architecture.md +696 -231
  266. package/documentation/auth.md +1069 -546
  267. package/documentation/code-mode.md +662 -356
  268. package/documentation/deploying.md +146 -0
  269. package/documentation/integrating.md +159 -0
  270. package/documentation/meta-tools.md +570 -216
  271. package/documentation/operating.md +135 -0
  272. package/documentation/operator-tests.md +97 -0
  273. package/documentation/operator-ui.md +132 -0
  274. package/documentation/provider-migration-0.29.md +65 -0
  275. package/examples/worker/AGENTS.md +34 -12
  276. package/examples/worker/README.md +293 -255
  277. package/examples/worker/kv-to-d1.wrangler.jsonc +26 -0
  278. package/examples/worker/scripts/copy-kv-to-d1.mjs +162 -0
  279. package/examples/worker/src/connecta.config.ts +151 -0
  280. package/examples/worker/src/index.ts +14 -197
  281. package/examples/worker/wrangler.jsonc +15 -35
  282. package/package.json +126 -54
  283. package/templates/node/.dockerignore +1 -2
  284. package/templates/node/.env.example +20 -21
  285. package/templates/node/AGENTS.md +21 -17
  286. package/templates/node/Dockerfile +3 -5
  287. package/templates/node/README.md +140 -58
  288. package/templates/node/docker-compose.yml +11 -12
  289. package/templates/node/package.json +4 -2
  290. package/templates/node/src/connecta.config.ts +119 -0
  291. package/templates/node/src/index.ts +9 -154
  292. package/templates/node/src/provision-token.ts +17 -0
  293. package/AGENTS.md +0 -170
  294. package/dist/artifacts/connector.d.ts +0 -58
  295. package/dist/artifacts/connector.js +0 -730
  296. package/dist/artifacts/document.js +0 -86
  297. package/dist/artifacts/guide.js +0 -140
  298. package/dist/artifacts/html-scan.js +0 -329
  299. package/dist/artifacts/json.js +0 -78
  300. package/dist/artifacts/kv-store.d.ts +0 -20
  301. package/dist/artifacts/kv-store.js +0 -140
  302. package/dist/artifacts/markdown.js +0 -478
  303. package/dist/artifacts/module.d.ts +0 -37
  304. package/dist/artifacts/module.js +0 -66
  305. package/dist/artifacts/operations.d.ts +0 -197
  306. package/dist/artifacts/operations.js +0 -1002
  307. package/dist/artifacts/refresh.d.ts +0 -55
  308. package/dist/artifacts/refresh.js +0 -236
  309. package/dist/artifacts/routes.js +0 -206
  310. package/dist/artifacts/types.d.ts +0 -214
  311. package/dist/artifacts/types.js +0 -22
  312. package/dist/artifacts/validate.d.ts +0 -62
  313. package/dist/artifacts/validate.js +0 -651
  314. package/dist/artifacts.d.ts +0 -6
  315. package/dist/artifacts.js +0 -9
  316. package/dist/auth/bearer.d.ts +0 -11
  317. package/dist/auth/bearer.js +0 -50
  318. package/dist/providers/basecamp.js +0 -445
  319. package/dist/providers/linear.js +0 -217
  320. package/dist/providers/mixpanel.js +0 -247
  321. package/dist/providers/revenuecat.js +0 -317
  322. package/dist/providers/stripe.js +0 -253
  323. package/dist/providers/vercel.js +0 -1253
  324. package/dist/storage/file.d.ts +0 -15
  325. package/dist/storage/file.js +0 -455
  326. package/ethos.md +0 -93
  327. package/examples/worker/src/cloudflare-kv.ts +0 -43
  328. package/examples/worker/src/d1-activity-row.ts +0 -155
  329. package/examples/worker/src/d1-activity.ts +0 -147
  330. package/examples/worker/src/d1-storage.ts +0 -138
  331. package/examples/worker/src/r2-artifact-blobs.ts +0 -29
  332. package/templates/node/src/file-activity.ts +0 -182
@@ -1,3 +1,4 @@
1
+ import { skill } from "./skill.generated.js";
1
2
  /**
2
3
  * No official `cloudflare` SDK on purpose. What the SDK sells — typed request
3
4
  * wrappers and pagination helpers — is what this connection replaces: an agent
@@ -6,14 +7,17 @@
6
7
  * optional peer, an install step, and an import that never belongs in the root
7
8
  * graph. Cloudflare's v4 API is authenticated `fetch` over a uniform
8
9
  * `{ success, errors, messages, result, result_info }` envelope, so Web APIs
9
- * alone keep this provider Workers-clean. `test/package-surface.test.ts` pins
10
+ * alone keep this provider Workers-clean. `test/package-surface.node.test.ts` pins
10
11
  * it: no `cloudflare` package in any dependency field, every import relative.
11
12
  */
12
- import { apiConnector as api, defined } from "../connectors/api-connector.js";
13
- import { remoteMcp, withCredentialDefaults, } from "../connectors/remote-mcp.js";
14
- import { vettedCatalog, withVettedCatalog } from "../catalog-drift.js";
15
- import { guardedFetch, retryAfterMs, } from "../connectors/guarded-fetch.js";
16
- import { ConnectorCallError } from "../errors.js";
13
+ import { apiConnector as api } from "../../connectors/api-connector.js";
14
+ import { remoteMcp, withCredentialDefaults } from "../../connectors/remote-mcp.js";
15
+ import { reviewedCatalog } from "../../catalog-drift.js";
16
+ import { guardedFetch, retryAfterMs, } from "../../connectors/guarded-fetch.js";
17
+ import { ConnectorCallError } from "../../errors.js";
18
+ import { keys, optionsOf, variants } from "../../config-schema.js";
19
+ import { CREDENTIAL, PROVIDER_COMMON, REMOTE_MCP_AUTH } from "../../connectors/option-shapes.js";
20
+ import { defineProvider } from "../../provider.js";
17
21
  /** Cloudflare's v4 REST base. Override only for a proxy or a test double. */
18
22
  export const CLOUDFLARE_API_BASE = "https://api.cloudflare.com/client/v4";
19
23
  /** Cloudflare's official whole-API hosted MCP endpoint. */
@@ -92,7 +96,7 @@ function admissionPolicy(maxConcurrency) {
92
96
  }
93
97
  const API_TOKEN_CREDENTIAL = {
94
98
  label: "Cloudflare API token",
95
- description: "A scoped API token (My Profile → API Tokens → Create Token), not a Global API Key. Grant only the permissions the deployment needs: zone-scoped \"Zone Read\", \"Zone Settings Write\", \"DNS Write\", \"Cache Purge\", and the phase-specific Rules product Read permissions as needed; account-scoped \"Workers Scripts Read/Write\", \"Workers KV Storage Read/Write\", \"Workers R2 Storage Read/Write\", or \"Cloudflare Pages Read/Write\" for the platform tools.",
99
+ description: 'A scoped API token (My Profile → API Tokens → Create Token), not a Global API Key. Grant only the permissions the deployment needs: zone-scoped "Zone Read", "Zone Settings Write", "DNS Write", "Cache Purge", and the phase-specific Rules product Read permissions as needed; account-scoped "Workers Scripts Read/Write", "Workers KV Storage Read/Write", "Workers R2 Storage Read/Write", or "Cloudflare Pages Read/Write" for the platform tools.',
96
100
  placeholder: "Paste API token",
97
101
  };
98
102
  const GLOBAL_API_KEY_CREDENTIAL = {
@@ -137,9 +141,7 @@ function credentialConfig(authentication, override) {
137
141
  return credential;
138
142
  }
139
143
  function asRecord(value) {
140
- return value && typeof value === "object" && !Array.isArray(value)
141
- ? value
142
- : {};
144
+ return value && typeof value === "object" && !Array.isArray(value) ? value : {};
143
145
  }
144
146
  function asArray(value) {
145
147
  return Array.isArray(value) ? value : [];
@@ -222,9 +224,12 @@ function failureFor(status, headers, errors) {
222
224
  { retryAfterMs: wait ?? 300_000 });
223
225
  }
224
226
  const authCoded = [...codes].some((code) => AUTH_ERROR_CODES.has(code));
225
- if (status === 401 || status === 403 || authCoded) {
227
+ if (status === 401 || authCoded) {
226
228
  return new ConnectorCallError("auth_required", `Cloudflare rejected the configured credential (HTTP ${status}). ${detail} Check that it is valid and has permission to access this resource.`);
227
229
  }
230
+ if (status === 403) {
231
+ return new ConnectorCallError("provider_permission_denied", `Cloudflare refused access to this resource (HTTP 403). ${detail} Verify the configured credential first; if valid, ask the account administrator to grant the required resource permission and token scope.`);
232
+ }
228
233
  if (status === 400 || status === 409 || status === 422) {
229
234
  return new ConnectorCallError("invalid_args", `Cloudflare rejected the request (HTTP ${status}). ${detail}`);
230
235
  }
@@ -286,24 +291,20 @@ function cloudflareTransport(baseUrl, authentication) {
286
291
  async function callCloudflare(send, spec, ctx) {
287
292
  return await send(spec, ctx, async (response) => {
288
293
  const parsed = await response.jsonResult();
289
- const envelope = "value" in parsed
290
- ? parsed.value
291
- : undefined;
294
+ const envelope = "value" in parsed ? parsed.value : undefined;
292
295
  if (envelope === undefined) {
293
296
  // A gateway error page or an empty body, not an envelope: the status is
294
297
  // the only real signal left.
298
+ // No cause: the parser's error quotes the body it could not read.
295
299
  throw response.ok
296
- ? new ConnectorCallError("unavailable", "Cloudflare returned a non-JSON body for a successful status.", "parseError" in parsed ? { cause: parsed.parseError } : {})
300
+ ? new ConnectorCallError("unavailable", "Cloudflare returned a non-JSON body for a successful status.")
297
301
  : failureFor(response.status, response.headers, []);
298
302
  }
299
303
  const errors = Array.isArray(envelope.errors) ? envelope.errors : [];
300
304
  if (!response.ok || envelope.success === false) {
301
305
  throw failureFor(response.status, response.headers, errors);
302
306
  }
303
- const isV4Envelope = "success" in envelope ||
304
- "result" in envelope ||
305
- "result_info" in envelope ||
306
- "messages" in envelope;
307
+ const isV4Envelope = "success" in envelope || "result" in envelope || "result_info" in envelope || "messages" in envelope;
307
308
  return {
308
309
  // `/graphql` and a small number of product APIs return ordinary JSON
309
310
  // instead of the standard v4 envelope. Preserve that document whole so
@@ -338,10 +339,7 @@ async function callCloudflareContent(send, spec, ctx, responseType) {
338
339
  let envelope;
339
340
  try {
340
341
  const parsed = await response.jsonResult();
341
- envelope =
342
- "value" in parsed
343
- ? parsed.value
344
- : undefined;
342
+ envelope = "value" in parsed ? parsed.value : undefined;
345
343
  }
346
344
  catch {
347
345
  // Content reads classify an already-failed status even when its error
@@ -369,9 +367,7 @@ function pageInfo(info) {
369
367
  return undefined;
370
368
  // A cursor-only result_info carries no page counters; inventing them would
371
369
  // report `hasMore: false` on a listing that has more.
372
- if (info.page === undefined &&
373
- info.total_pages === undefined &&
374
- info.count === undefined) {
370
+ if (info.page === undefined && info.total_pages === undefined && info.count === undefined) {
375
371
  return undefined;
376
372
  }
377
373
  const page = typeof info.page === "number" ? info.page : 1;
@@ -392,9 +388,7 @@ function pagedList(key, project) {
392
388
  });
393
389
  }
394
390
  function cursorResult(cursor) {
395
- return typeof cursor === "string" && cursor !== ""
396
- ? { nextCursor: cursor }
397
- : {};
391
+ return typeof cursor === "string" && cursor !== "" ? { nextCursor: cursor } : {};
398
392
  }
399
393
  function projectAccount(value) {
400
394
  const account = asRecord(value);
@@ -409,9 +403,7 @@ function projectZone(value) {
409
403
  const zone = asRecord(value);
410
404
  const account = asRecord(zone["account"]);
411
405
  const plan = asRecord(zone["plan"]);
412
- const nameServers = Array.isArray(zone["name_servers"])
413
- ? zone["name_servers"]
414
- : undefined;
406
+ const nameServers = Array.isArray(zone["name_servers"]) ? zone["name_servers"] : undefined;
415
407
  return compact({
416
408
  id: zone["id"],
417
409
  name: zone["name"],
@@ -438,9 +430,7 @@ function projectZoneSetting(value) {
438
430
  function projectDnsRecord(value) {
439
431
  const record = asRecord(value);
440
432
  const comment = record["comment"] ? record["comment"] : undefined;
441
- const tags = Array.isArray(record["tags"]) && record["tags"].length > 0
442
- ? record["tags"]
443
- : undefined;
433
+ const tags = Array.isArray(record["tags"]) && record["tags"].length > 0 ? record["tags"] : undefined;
444
434
  return compact({
445
435
  id: record["id"],
446
436
  name: record["name"],
@@ -491,13 +481,6 @@ function projectKvBulkValues(value) {
491
481
  const result = asRecord(value);
492
482
  return compact({ values: result["values"] });
493
483
  }
494
- function projectKvBulkResult(value) {
495
- const result = asRecord(value);
496
- return compact({
497
- successfulKeyCount: result["successful_key_count"],
498
- unsuccessfulKeys: result["unsuccessful_keys"],
499
- });
500
- }
501
484
  function projectR2Bucket(value) {
502
485
  const bucket = asRecord(value);
503
486
  return compact({
@@ -582,9 +565,7 @@ function projectRuleset(value) {
582
565
  function projectPagesProject(value) {
583
566
  const project = asRecord(value);
584
567
  const latest = asRecord(project["latest_deployment"]);
585
- const domains = Array.isArray(project["domains"])
586
- ? project["domains"]
587
- : undefined;
568
+ const domains = Array.isArray(project["domains"]) ? project["domains"] : undefined;
588
569
  const latestDeployment = latest["id"] === undefined
589
570
  ? undefined
590
571
  : compact({
@@ -636,9 +617,7 @@ const RAW_INPUT_PROPERTY = {
636
617
  */
637
618
  function pagingInputProperties(minPerPage, maxPerPage, options = {}) {
638
619
  const { defaultPerPage, bounds = "cloudflare" } = options;
639
- const defaultNote = defaultPerPage === undefined
640
- ? " Cloudflare chooses the default."
641
- : ` Defaults to ${defaultPerPage}.`;
620
+ const defaultNote = defaultPerPage === undefined ? " Cloudflare chooses the default." : ` Defaults to ${defaultPerPage}.`;
642
621
  const boundsNote = bounds === "clamped"
643
622
  ? ` The ${maxPerPage} ceiling is this connection's cap, not Cloudflare's limit.`
644
623
  : bounds === "undocumented"
@@ -768,9 +747,7 @@ function scopeRequired(kind, fallback) {
768
747
  }
769
748
  function optionalString(args, key) {
770
749
  const value = args[key];
771
- return typeof value === "string" && value.trim() !== ""
772
- ? value.trim()
773
- : undefined;
750
+ return typeof value === "string" && value.trim() !== "" ? value.trim() : undefined;
774
751
  }
775
752
  function optionalNumber(args, key) {
776
753
  const value = args[key];
@@ -782,17 +759,6 @@ function requireString(args, key) {
782
759
  return value;
783
760
  throw new ConnectorCallError("invalid_args", `${key} must not be blank.`);
784
761
  }
785
- function encodeObjectKey(value) {
786
- return value
787
- .split("/")
788
- .map((segment) => {
789
- if (segment === "." || segment === "..") {
790
- throw new ConnectorCallError("invalid_args", "objectKey cannot contain '.' or '..' path segments because URL normalization would change the target resource.");
791
- }
792
- return encodeURIComponent(segment);
793
- })
794
- .join("/");
795
- }
796
762
  function cloudflareApiPath(value) {
797
763
  if (typeof value !== "string") {
798
764
  throw new ConnectorCallError("invalid_args", "path must be a string.");
@@ -864,7 +830,11 @@ function headersFromArgs(value) {
864
830
  if (forbidden.has(name.toLowerCase())) {
865
831
  throw new ConnectorCallError("invalid_args", `The raw Cloudflare tools do not allow the ${name} header. Authentication and request framing are connector-owned; use contentType for a raw upload body.`);
866
832
  }
867
- headers[name] = String(entry["value"]);
833
+ const value = String(entry["value"]);
834
+ if (name.toLowerCase() === "cf-r2-storage-class" && value !== "Standard" && value !== "InfrequentAccess") {
835
+ throw new ConnectorCallError("invalid_args", "cf-r2-storage-class must be Standard or InfrequentAccess.");
836
+ }
837
+ headers[name] = value;
868
838
  }
869
839
  return headers;
870
840
  }
@@ -875,6 +845,32 @@ function rawSpec(args) {
875
845
  headers: headersFromArgs(args["headers"]),
876
846
  });
877
847
  }
848
+ // The hosted helper cannot send R2 jurisdiction or bucket-edit storage headers.
849
+ // Ordinary headers such as Accept are not a reason to restore JSON duplicates.
850
+ function needsMutationHeaders(method, spec) {
851
+ const headers = Object.entries(spec.headers ?? {});
852
+ if (method === "PATCH" &&
853
+ /^\/accounts\/[^/]+\/r2\/buckets\/[^/]+\/?$/.test(spec.path) &&
854
+ headers.some(([name, value]) => name.toLowerCase() === "cf-r2-storage-class" && (value === "Standard" || value === "InfrequentAccess")))
855
+ return true;
856
+ return (/^\/accounts\/[^/]+\/r2\/buckets(?:\/|$)/.test(spec.path) &&
857
+ headers.some(([name, value]) => name.toLowerCase() === "cf-r2-jurisdiction" && typeof value === "string" && value.trim().length > 0));
858
+ }
859
+ // Reviewed raw-body families, rather than a Content-Type assertion that could
860
+ // send an ordinary JSON DNS/configuration mutation through the upload tool.
861
+ function isUploadEndpoint(method, path) {
862
+ const match = /^\/accounts\/[^/]+(\/.*)$/.exec(path);
863
+ if (!match)
864
+ return false;
865
+ const accountPath = match[1];
866
+ if (method === "PUT") {
867
+ return /^(?:\/workers\/scripts\/[^/]+|\/storage\/kv\/namespaces\/[^/]+\/values\/.+|\/r2\/buckets\/[^/]+\/objects\/.+)\/?$/.test(accountPath);
868
+ }
869
+ if (method === "POST") {
870
+ return /^(?:\/workers\/scripts\/[^/]+\/versions|\/images\/v1|\/stream|\/pages\/assets\/upload|\/pages\/projects\/[^/]+\/deployments)\/?$/.test(accountPath);
871
+ }
872
+ return false;
873
+ }
878
874
  function r2Headers(args) {
879
875
  return { "cf-r2-jurisdiction": optionalString(args, "jurisdiction") };
880
876
  }
@@ -884,7 +880,9 @@ function bytesFromBase64(value) {
884
880
  return Uint8Array.from(binary, (character) => character.charCodeAt(0));
885
881
  }
886
882
  catch (cause) {
887
- throw new ConnectorCallError("invalid_args", "base64Body and multipart file base64 values must be valid base64.", { cause });
883
+ throw new ConnectorCallError("invalid_args", "base64Body and multipart file base64 values must be valid base64.", {
884
+ cause,
885
+ });
888
886
  }
889
887
  }
890
888
  function uploadBody(args) {
@@ -917,7 +915,9 @@ function uploadBody(args) {
917
915
  if (Number(text !== undefined) + Number(base64 !== undefined) !== 1) {
918
916
  throw new ConnectorCallError("invalid_args", "Each multipart file needs exactly one of text or base64.");
919
917
  }
920
- const blob = new Blob([text ?? bytesFromBase64(base64)], { type: String(file["contentType"] ?? "application/octet-stream") });
918
+ const blob = new Blob([text ?? bytesFromBase64(base64)], {
919
+ type: String(file["contentType"] ?? "application/octet-stream"),
920
+ });
921
921
  form.append(String(file["name"]), blob, String(file["fileName"]));
922
922
  }
923
923
  return { rawBody: form };
@@ -1007,13 +1007,6 @@ const KV_BULK_VALUES_SCHEMA = {
1007
1007
  },
1008
1008
  required: ["values"],
1009
1009
  };
1010
- const KV_BULK_RESULT_SCHEMA = {
1011
- type: "object",
1012
- properties: {
1013
- successfulKeyCount: { type: "number" },
1014
- unsuccessfulKeys: { type: "array", items: { type: "string" } },
1015
- },
1016
- };
1017
1010
  const R2_CORS_SCHEMA = {
1018
1011
  type: "object",
1019
1012
  properties: {
@@ -1083,7 +1076,7 @@ const QUERY_INPUT_PROPERTY = {
1083
1076
  // cloudflare_api_upload's compact input past the 1,024-byte discovery budget.
1084
1077
  const HEADERS_INPUT_PROPERTY = {
1085
1078
  type: "array",
1086
- description: "Endpoint headers as name/value pairs, e.g. cf-r2-jurisdiction or Range. Connector-owned headers are refused.",
1079
+ description: "Endpoint headers as name/value pairs, e.g. cf-r2-jurisdiction, cf-r2-storage-class (Standard or InfrequentAccess), or Range. Connector-owned headers are refused.",
1087
1080
  items: {
1088
1081
  type: "object",
1089
1082
  properties: {
@@ -1099,11 +1092,6 @@ const R2_JURISDICTION_PROPERTY = {
1099
1092
  enum: ["default", "eu", "us", "fedramp"],
1100
1093
  description: "Bucket jurisdiction. Omit for ordinary buckets; set eu, us, or fedramp for jurisdictional buckets.",
1101
1094
  };
1102
- const KV_JURISDICTION_PROPERTY = {
1103
- type: "string",
1104
- enum: ["eu", "fedramp", "us"],
1105
- description: "Creation-only namespace jurisdiction. Omit for an ordinary namespace; it cannot be changed by rename_kv_namespace.",
1106
- };
1107
1095
  const R2_BUCKET_NAME_PROPERTY = {
1108
1096
  type: "string",
1109
1097
  minLength: 3,
@@ -1140,26 +1128,6 @@ const WORKER_DEPLOYMENT_ID_PROPERTY = {
1140
1128
  minLength: 1,
1141
1129
  description: "Deployment id from list_worker_deployments.",
1142
1130
  };
1143
- const RETRY_DEPLOYMENT_ID_PROPERTY = {
1144
- type: "string",
1145
- minLength: 1,
1146
- description: "Deployment id to retry.",
1147
- };
1148
- const ROLLBACK_DEPLOYMENT_ID_PROPERTY = {
1149
- type: "string",
1150
- minLength: 1,
1151
- description: "Previous deployment id to promote.",
1152
- };
1153
- const DELETE_DEPLOYMENT_ID_PROPERTY = {
1154
- type: "string",
1155
- minLength: 1,
1156
- description: "Deployment id to delete.",
1157
- };
1158
- const DELETE_PROJECT_NAME_PROPERTY = {
1159
- type: "string",
1160
- minLength: 1,
1161
- description: "Pages project name to delete.",
1162
- };
1163
1131
  const SETTING_ID_PROPERTY = {
1164
1132
  type: "string",
1165
1133
  minLength: 1,
@@ -1206,9 +1174,7 @@ function cfTool(name, description, annotations, scopeKind, scopeFallback, proper
1206
1174
  ...properties,
1207
1175
  }
1208
1176
  : properties,
1209
- required: scopeKind
1210
- ? [...scopeRequired(scopeKind, scopeFallback), ...required]
1211
- : required,
1177
+ required: scopeKind ? [...scopeRequired(scopeKind, scopeFallback), ...required] : required,
1212
1178
  additionalProperties: false,
1213
1179
  },
1214
1180
  outputSchema,
@@ -1221,13 +1187,6 @@ function getResult(send, request, project = (result) => result) {
1221
1187
  return project(result, args);
1222
1188
  };
1223
1189
  }
1224
- function deleteAck(send, key, value, request) {
1225
- return async (args, ctx) => {
1226
- const id = value(args);
1227
- await callCloudflare(send, request(args, id), ctx);
1228
- return { deleted: true, [key]: id };
1229
- };
1230
- }
1231
1190
  function buildTools(scope, authentication) {
1232
1191
  const { send } = scope;
1233
1192
  const zoneArg = (args) => requireScope(args["zoneId"], scope.zoneId, "zoneId");
@@ -1241,7 +1200,7 @@ function buildTools(scope, authentication) {
1241
1200
  id: { type: "string" },
1242
1201
  status: {
1243
1202
  type: "string",
1244
- description: "\"active\" for a usable token.",
1203
+ description: '"active" for a usable token.',
1245
1204
  },
1246
1205
  notBefore: { type: "string" },
1247
1206
  expiresOn: { type: "string" },
@@ -1264,7 +1223,7 @@ function buildTools(scope, authentication) {
1264
1223
  email: { type: "string" },
1265
1224
  status: {
1266
1225
  type: "string",
1267
- description: "\"active\" when Cloudflare accepts the email and key.",
1226
+ description: '"active" when Cloudflare accepts the email and key.',
1268
1227
  },
1269
1228
  },
1270
1229
  required: ["email", "status"],
@@ -1285,7 +1244,7 @@ function buildTools(scope, authentication) {
1285
1244
  type: "string",
1286
1245
  enum: ["json", "text", "base64"],
1287
1246
  description: "How to read a successful response. Defaults to json; use text or base64 for object, log, script, and media downloads.",
1288
- }
1247
+ },
1289
1248
  }, ["path"], {
1290
1249
  type: "object",
1291
1250
  properties: {
@@ -1318,7 +1277,7 @@ function buildTools(scope, authentication) {
1318
1277
  resultInfo,
1319
1278
  });
1320
1279
  }),
1321
- cfTool("cloudflare_api_mutate", "Call any JSON POST, PUT, PATCH, or DELETE endpoint under Cloudflare's v4 API with this connector's credential. The approval-gated write hatch for products the named surface does not reach. No multipart or binary uploads.", { readOnlyHint: false, destructiveHint: true }, undefined, undefined, {
1280
+ cfTool("cloudflare_api_mutate", "Call JSON mutations for Global API Key identity or R2 jurisdiction/storage-class header gaps. Ordinary API-token writes use hosted execute. Bucket PATCH accepts cf-r2-storage-class: Standard or InfrequentAccess; no uploads.", { readOnlyHint: false, destructiveHint: true }, undefined, undefined, {
1322
1281
  method: {
1323
1282
  type: "string",
1324
1283
  enum: ["POST", "PUT", "PATCH", "DELETE"],
@@ -1334,7 +1293,7 @@ function buildTools(scope, authentication) {
1334
1293
  body: {
1335
1294
  type: ["object", "array", "string", "number", "boolean", "null"],
1336
1295
  description: "JSON request body exactly as documented by Cloudflare. Omit for endpoints with no body.",
1337
- }
1296
+ },
1338
1297
  }, ["method", "path"], {
1339
1298
  type: "object",
1340
1299
  properties: {
@@ -1350,6 +1309,9 @@ function buildTools(scope, authentication) {
1350
1309
  }, async (args, ctx) => {
1351
1310
  const method = String(args["method"]);
1352
1311
  const spec = rawSpec(args);
1312
+ if (authentication === "apiToken" && !needsMutationHeaders(method, spec)) {
1313
+ throw new ConnectorCallError("invalid_args", "Use the Cloudflare MCP execute tool for JSON mutations with an API token. This REST tool is reserved for Global API Key identity, R2 bucket operations requiring cf-r2-jurisdiction, or bucket PATCH with cf-r2-storage-class set to Standard or InfrequentAccess.");
1314
+ }
1353
1315
  const { result, resultInfo } = await callCloudflare(send, compact({
1354
1316
  method,
1355
1317
  ...spec,
@@ -1416,7 +1378,7 @@ function buildTools(scope, authentication) {
1416
1378
  required: ["name", "fileName", "contentType"],
1417
1379
  additionalProperties: false,
1418
1380
  },
1419
- }
1381
+ },
1420
1382
  }, ["method", "path"], {
1421
1383
  type: "object",
1422
1384
  properties: {
@@ -1427,6 +1389,9 @@ function buildTools(scope, authentication) {
1427
1389
  required: ["result"],
1428
1390
  }, async (args, ctx) => {
1429
1391
  const spec = rawSpec(args);
1392
+ if (authentication === "apiToken" && !isUploadEndpoint(args["method"], spec.path)) {
1393
+ throw new ConnectorCallError("invalid_args", "Use the Cloudflare MCP execute tool for this mutation. REST uploads are confined to Workers, KV values, R2 objects, Images, Stream and Pages upload endpoints.");
1394
+ }
1430
1395
  const upload = uploadBody(args);
1431
1396
  const { result } = await callCloudflare(send, compact({
1432
1397
  method: String(args["method"]),
@@ -1444,7 +1409,7 @@ function buildTools(scope, authentication) {
1444
1409
  description: "Filter by exact account name.",
1445
1410
  },
1446
1411
  ...pagingInputProperties(5, 50, { defaultPerPage: 20 }),
1447
- raw: RAW_INPUT_PROPERTY
1412
+ raw: RAW_INPUT_PROPERTY,
1448
1413
  }, [], listOutputSchema("accounts", ACCOUNT_SCHEMA), async (args, ctx) => {
1449
1414
  const { result, resultInfo } = await callCloudflare(send, {
1450
1415
  method: "GET",
@@ -1472,7 +1437,7 @@ function buildTools(scope, authentication) {
1472
1437
  description: "Filter by zone status.",
1473
1438
  },
1474
1439
  ...pagingInputProperties(5, 50, { defaultPerPage: 20 }),
1475
- raw: RAW_INPUT_PROPERTY
1440
+ raw: RAW_INPUT_PROPERTY,
1476
1441
  }, [], listOutputSchema("zones", ZONE_SCHEMA), async (args, ctx) => {
1477
1442
  const { result, resultInfo } = await callCloudflare(send, {
1478
1443
  method: "GET",
@@ -1493,8 +1458,8 @@ function buildTools(scope, authentication) {
1493
1458
  return pagedList("zones", projectZone)(result, resultInfo, args["raw"] === true);
1494
1459
  }),
1495
1460
  cfTool("get_zone", "Fetch one zone's settings summary by id: status, plan, name servers, and owning account.", readOnly, "zoneId", scope.zoneId, {
1496
- raw: RAW_INPUT_PROPERTY
1497
- }, [], ZONE_SCHEMA, getResult(send, (args) => ({ method: "GET", path: `/zones/${encodeURIComponent(zoneArg(args))}` }), (result, args) => args["raw"] === true ? result : projectZone(result))),
1461
+ raw: RAW_INPUT_PROPERTY,
1462
+ }, [], ZONE_SCHEMA, getResult(send, (args) => ({ method: "GET", path: `/zones/${encodeURIComponent(zoneArg(args))}` }), (result, args) => (args["raw"] === true ? result : projectZone(result)))),
1498
1463
  // No bulk `list_zone_settings` on purpose (#361): Cloudflare's published
1499
1464
  // document marks GET /zones/{zoneId}/settings and its PATCH sibling
1500
1465
  // deprecated with no bulk replacement, and the tool projected nothing while
@@ -1504,26 +1469,11 @@ function buildTools(scope, authentication) {
1504
1469
  // the caller's risk rather than promised by connecta's catalog.
1505
1470
  cfTool("get_zone_setting", "Get one zone setting by its Cloudflare setting id, such as ssl, always_use_https, min_tls_version, brotli, or development_mode.", readOnly, "zoneId", scope.zoneId, {
1506
1471
  settingId: SETTING_ID_PROPERTY,
1507
- raw: RAW_INPUT_PROPERTY
1472
+ raw: RAW_INPUT_PROPERTY,
1508
1473
  }, ["settingId"], ZONE_SETTING_SCHEMA, getResult(send, (args) => ({
1509
1474
  method: "GET",
1510
1475
  path: `/zones/${encodeURIComponent(zoneArg(args))}/settings/${encodeURIComponent(requireString(args, "settingId"))}`,
1511
- }), (result, args) => args["raw"] === true ? result : projectZoneSetting(result))),
1512
- cfTool("update_zone_setting", "Set one editable zone setting. Read it first: allowed value types and plan restrictions differ by setting.", { readOnlyHint: false, destructiveHint: true }, "zoneId", scope.zoneId, {
1513
- settingId: SETTING_ID_PROPERTY,
1514
- value: {
1515
- type: ["string", "number", "boolean", "array"],
1516
- description: "New setting value in the type returned by get_zone_setting. Arrays must contain strings.",
1517
- items: { type: "string" },
1518
- }
1519
- }, ["settingId", "value"], ZONE_SETTING_SCHEMA, async (args, ctx) => {
1520
- const { result } = await callCloudflare(send, {
1521
- method: "PATCH",
1522
- path: `/zones/${encodeURIComponent(zoneArg(args))}/settings/${encodeURIComponent(requireString(args, "settingId"))}`,
1523
- body: { value: args["value"] },
1524
- }, ctx);
1525
- return projectZoneSetting(result);
1526
- }),
1476
+ }), (result, args) => (args["raw"] === true ? result : projectZoneSetting(result)))),
1527
1477
  cfTool("list_zone_rulesets", "List zone rulesets for WAF, redirects, transforms, cache rules, configuration rules, and other Ruleset Engine phases.", readOnly, "zoneId", scope.zoneId, {
1528
1478
  perPage: {
1529
1479
  type: "integer",
@@ -1531,7 +1481,7 @@ function buildTools(scope, authentication) {
1531
1481
  maximum: 50,
1532
1482
  description: "Rulesets per request, 1 to 50.",
1533
1483
  },
1534
- cursor: CURSOR_INPUT_PROPERTY
1484
+ cursor: CURSOR_INPUT_PROPERTY,
1535
1485
  }, [], {
1536
1486
  type: "object",
1537
1487
  properties: {
@@ -1559,7 +1509,7 @@ function buildTools(scope, authentication) {
1559
1509
  type: "string",
1560
1510
  minLength: 1,
1561
1511
  description: "Ruleset id from list_zone_rulesets.",
1562
- }
1512
+ },
1563
1513
  }, ["rulesetId"], RULESET_SCHEMA, getResult(send, (args) => ({
1564
1514
  method: "GET",
1565
1515
  path: `/zones/${encodeURIComponent(zoneArg(args))}/rulesets/${encodeURIComponent(requireString(args, "rulesetId"))}`,
@@ -1595,7 +1545,7 @@ function buildTools(scope, authentication) {
1595
1545
  defaultPerPage: 100,
1596
1546
  bounds: "clamped",
1597
1547
  }),
1598
- raw: RAW_INPUT_PROPERTY
1548
+ raw: RAW_INPUT_PROPERTY,
1599
1549
  }, [], listOutputSchema("records", DNS_RECORD_SCHEMA), async (args, ctx) => {
1600
1550
  const { result, resultInfo } = await callCloudflare(send, {
1601
1551
  method: "GET",
@@ -1614,13 +1564,13 @@ function buildTools(scope, authentication) {
1614
1564
  }),
1615
1565
  cfTool("get_dns_record", "Fetch one DNS record by its record id.", readOnly, "zoneId", scope.zoneId, {
1616
1566
  recordId: RECORD_ID_PROPERTY,
1617
- raw: RAW_INPUT_PROPERTY
1567
+ raw: RAW_INPUT_PROPERTY,
1618
1568
  }, ["recordId"], DNS_RECORD_SCHEMA, getResult(send, (args) => ({
1619
1569
  method: "GET",
1620
1570
  path: `/zones/${encodeURIComponent(zoneArg(args))}/dns_records/${encodeURIComponent(String(args["recordId"]))}`,
1621
- }), (result, args) => args["raw"] === true ? result : projectDnsRecord(result))),
1571
+ }), (result, args) => (args["raw"] === true ? result : projectDnsRecord(result)))),
1622
1572
  cfTool("list_worker_scripts", "List Workers scripts deployed in an account, with their last-modified times.", readOnly, "accountId", scope.accountId, {
1623
- raw: RAW_INPUT_PROPERTY
1573
+ raw: RAW_INPUT_PROPERTY,
1624
1574
  }, [], listOutputSchema("scripts", {
1625
1575
  type: "object",
1626
1576
  properties: {
@@ -1639,52 +1589,32 @@ function buildTools(scope, authentication) {
1639
1589
  }),
1640
1590
  cfTool("get_worker_settings", "Get a Worker's compatibility date and flags, bindings, limits, observability, placement, usage model, and other script settings.", readOnly, "accountId", scope.accountId, {
1641
1591
  scriptName: SCRIPT_NAME_PROPERTY,
1642
- raw: RAW_INPUT_PROPERTY
1592
+ raw: RAW_INPUT_PROPERTY,
1643
1593
  }, ["scriptName"], WORKER_SETTINGS_SCHEMA, getResult(send, (args) => ({
1644
1594
  method: "GET",
1645
1595
  path: `/accounts/${encodeURIComponent(accountArg(args))}/workers/scripts/${encodeURIComponent(requireString(args, "scriptName"))}/settings`,
1646
- }), (result, args) => args["raw"] === true ? result : projectWorkerSettings(result))),
1596
+ }), (result, args) => (args["raw"] === true ? result : projectWorkerSettings(result)))),
1647
1597
  cfTool("list_worker_deployments", "List deployments of a Worker script, including version traffic allocations and deployment strategy.", readOnly, "accountId", scope.accountId, {
1648
- scriptName: SCRIPT_NAME_PROPERTY
1598
+ scriptName: SCRIPT_NAME_PROPERTY,
1649
1599
  }, ["scriptName"], listOutputSchema("deployments", WORKER_DEPLOYMENT_SCHEMA), async (args, ctx) => {
1650
1600
  const { result } = await callCloudflare(send, {
1651
1601
  method: "GET",
1652
1602
  path: `/accounts/${encodeURIComponent(accountArg(args))}/workers/scripts/${encodeURIComponent(requireString(args, "scriptName"))}/deployments`,
1653
1603
  }, ctx);
1654
1604
  const record = asRecord(result);
1655
- const deployments = Array.isArray(result)
1656
- ? result
1657
- : asArray(record["deployments"]);
1605
+ const deployments = Array.isArray(result) ? result : asArray(record["deployments"]);
1658
1606
  return { deployments: deployments.map(projectWorkerDeployment) };
1659
1607
  }),
1660
1608
  cfTool("get_worker_deployment", "Get one Worker deployment and its version traffic allocations.", readOnly, "accountId", scope.accountId, {
1661
1609
  scriptName: SCRIPT_NAME_PROPERTY,
1662
- deploymentId: WORKER_DEPLOYMENT_ID_PROPERTY
1610
+ deploymentId: WORKER_DEPLOYMENT_ID_PROPERTY,
1663
1611
  }, ["scriptName", "deploymentId"], WORKER_DEPLOYMENT_SCHEMA, getResult(send, (args) => ({
1664
1612
  method: "GET",
1665
1613
  path: `/accounts/${encodeURIComponent(accountArg(args))}/workers/scripts/${encodeURIComponent(requireString(args, "scriptName"))}/deployments/${encodeURIComponent(requireString(args, "deploymentId"))}`,
1666
1614
  }), projectWorkerDeployment)),
1667
- cfTool("delete_worker_script", "Delete a Worker script and stop traffic served by that script. This cannot be undone from the API.", { readOnlyHint: false, destructiveHint: true }, "accountId", scope.accountId, {
1668
- scriptName: SCRIPT_NAME_PROPERTY,
1669
- force: {
1670
- type: "boolean",
1671
- description: "Pass Cloudflare's force=true option when the script has dependencies that permit forced removal.",
1672
- }
1673
- }, ["scriptName"], {
1674
- type: "object",
1675
- properties: {
1676
- deleted: { type: "boolean" },
1677
- scriptName: { type: "string" },
1678
- },
1679
- required: ["deleted", "scriptName"],
1680
- }, deleteAck(send, "scriptName", (args) => requireString(args, "scriptName"), (args, scriptName) => ({
1681
- method: "DELETE",
1682
- path: `/accounts/${encodeURIComponent(accountArg(args))}/workers/scripts/${encodeURIComponent(scriptName)}`,
1683
- query: { force: typeof args["force"] === "boolean" ? args["force"] : undefined },
1684
- }))),
1685
1615
  cfTool("list_kv_namespaces", "List Workers KV namespaces in an account, with the namespace ids bindings refer to.", readOnly, "accountId", scope.accountId, {
1686
1616
  ...pagingInputProperties(1, 1000, { defaultPerPage: 20 }),
1687
- raw: RAW_INPUT_PROPERTY
1617
+ raw: RAW_INPUT_PROPERTY,
1688
1618
  }, [], listOutputSchema("namespaces", {
1689
1619
  type: "object",
1690
1620
  properties: {
@@ -1706,61 +1636,11 @@ function buildTools(scope, authentication) {
1706
1636
  return pagedList("namespaces", projectKvNamespace)(result, resultInfo, args["raw"] === true);
1707
1637
  }),
1708
1638
  cfTool("get_kv_namespace", "Get one Workers KV namespace by id.", readOnly, "accountId", scope.accountId, {
1709
- namespaceId: NAMESPACE_ID_PROPERTY
1639
+ namespaceId: NAMESPACE_ID_PROPERTY,
1710
1640
  }, ["namespaceId"], KV_NAMESPACE_SCHEMA, getResult(send, (args) => ({
1711
1641
  method: "GET",
1712
1642
  path: `/accounts/${encodeURIComponent(accountArg(args))}/storage/kv/namespaces/${encodeURIComponent(requireString(args, "namespaceId"))}`,
1713
1643
  }), projectKvNamespace)),
1714
- cfTool("create_kv_namespace", "Create a Workers KV namespace.", { readOnlyHint: false }, "accountId", scope.accountId, {
1715
- title: {
1716
- type: "string",
1717
- minLength: 1,
1718
- maxLength: 512,
1719
- description: "Human-readable namespace title.",
1720
- },
1721
- jurisdiction: KV_JURISDICTION_PROPERTY
1722
- }, ["title"], KV_NAMESPACE_SCHEMA, async (args, ctx) => {
1723
- const { result } = await callCloudflare(send, {
1724
- method: "POST",
1725
- path: `/accounts/${encodeURIComponent(accountArg(args))}/storage/kv/namespaces`,
1726
- body: compact({
1727
- title: requireString(args, "title"),
1728
- jurisdiction: args["jurisdiction"],
1729
- }),
1730
- }, ctx);
1731
- return projectKvNamespace(result);
1732
- }),
1733
- cfTool("rename_kv_namespace", "Rename an existing Workers KV namespace without changing its id or keys.", { readOnlyHint: false, destructiveHint: true }, "accountId", scope.accountId, {
1734
- namespaceId: NAMESPACE_ID_PROPERTY,
1735
- title: {
1736
- type: "string",
1737
- minLength: 1,
1738
- maxLength: 512,
1739
- description: "Replacement namespace title.",
1740
- }
1741
- }, ["namespaceId", "title"], KV_NAMESPACE_SCHEMA, async (args, ctx) => {
1742
- const { result } = await callCloudflare(send, {
1743
- method: "PUT",
1744
- path: `/accounts/${encodeURIComponent(accountArg(args))}/storage/kv/namespaces/${encodeURIComponent(requireString(args, "namespaceId"))}`,
1745
- body: { title: requireString(args, "title") },
1746
- }, ctx);
1747
- return result
1748
- ? projectKvNamespace(result)
1749
- : { renamed: true, namespaceId: args["namespaceId"] };
1750
- }),
1751
- cfTool("delete_kv_namespace", "Permanently delete a Workers KV namespace and every key stored in it.", { readOnlyHint: false, destructiveHint: true }, "accountId", scope.accountId, {
1752
- namespaceId: NAMESPACE_ID_PROPERTY
1753
- }, ["namespaceId"], {
1754
- type: "object",
1755
- properties: {
1756
- deleted: { type: "boolean" },
1757
- namespaceId: { type: "string" },
1758
- },
1759
- required: ["deleted", "namespaceId"],
1760
- }, deleteAck(send, "namespaceId", (args) => requireString(args, "namespaceId"), (args, namespaceId) => ({
1761
- method: "DELETE",
1762
- path: `/accounts/${encodeURIComponent(accountArg(args))}/storage/kv/namespaces/${encodeURIComponent(namespaceId)}`,
1763
- }))),
1764
1644
  cfTool("list_kv_keys", "List keys and metadata in a Workers KV namespace by prefix, using cursor pagination.", readOnly, "accountId", scope.accountId, {
1765
1645
  namespaceId: NAMESPACE_ID_PROPERTY,
1766
1646
  prefix: {
@@ -1773,7 +1653,7 @@ function buildTools(scope, authentication) {
1773
1653
  maximum: 1000,
1774
1654
  description: "Keys per request, 10 to 1000. Defaults to 1000.",
1775
1655
  },
1776
- cursor: CURSOR_INPUT_PROPERTY
1656
+ cursor: CURSOR_INPUT_PROPERTY,
1777
1657
  }, ["namespaceId"], {
1778
1658
  type: "object",
1779
1659
  properties: {
@@ -1825,7 +1705,7 @@ function buildTools(scope, authentication) {
1825
1705
  type: "string",
1826
1706
  enum: ["text", "json"],
1827
1707
  description: "Return strings as stored, or parse JSON values before returning them.",
1828
- }
1708
+ },
1829
1709
  }, ["namespaceId", "keys"], KV_BULK_VALUES_SCHEMA, async (args, ctx) => {
1830
1710
  const { result } = await callCloudflare(send, {
1831
1711
  method: "POST",
@@ -1838,74 +1718,6 @@ function buildTools(scope, authentication) {
1838
1718
  }, ctx);
1839
1719
  return projectKvBulkValues(result);
1840
1720
  }),
1841
- cfTool("bulk_write_kv_values", "Create or replace multiple Workers KV values, with optional expirations and JSON metadata.", { readOnlyHint: false, destructiveHint: true }, "accountId", scope.accountId, {
1842
- namespaceId: NAMESPACE_ID_PROPERTY,
1843
- entries: {
1844
- type: "array",
1845
- minItems: 1,
1846
- maxItems: 10_000,
1847
- description: "Key/value entries to write, up to Cloudflare's 10,000-key bulk limit.",
1848
- items: {
1849
- type: "object",
1850
- properties: {
1851
- key: {
1852
- type: "string",
1853
- minLength: 1,
1854
- maxLength: 512,
1855
- description: "Key name, up to Cloudflare's 512 bytes.",
1856
- },
1857
- value: {
1858
- type: "string",
1859
- maxLength: 26_214_400,
1860
- description: "Value, up to Cloudflare's 25 MiB.",
1861
- },
1862
- expiration: {
1863
- type: "number",
1864
- description: "Absolute expiry as a Unix timestamp in seconds.",
1865
- },
1866
- expiration_ttl: {
1867
- type: "number",
1868
- minimum: 60,
1869
- description: "Relative expiry in seconds; Cloudflare's floor is 60.",
1870
- },
1871
- metadata: {
1872
- type: ["object", "array", "string", "number", "boolean", "null"],
1873
- description: "JSON metadata returned beside the key by list_kv_keys.",
1874
- },
1875
- base64: {
1876
- type: "boolean",
1877
- description: "Treat value as base64 and store the decoded bytes.",
1878
- },
1879
- },
1880
- required: ["key", "value"],
1881
- additionalProperties: false,
1882
- },
1883
- }
1884
- }, ["namespaceId", "entries"], KV_BULK_RESULT_SCHEMA, async (args, ctx) => {
1885
- const { result } = await callCloudflare(send, {
1886
- method: "PUT",
1887
- path: `/accounts/${encodeURIComponent(accountArg(args))}/storage/kv/namespaces/${encodeURIComponent(requireString(args, "namespaceId"))}/bulk`,
1888
- body: args["entries"],
1889
- }, ctx);
1890
- return projectKvBulkResult(result);
1891
- }),
1892
- cfTool("bulk_delete_kv_values", "Permanently delete multiple keys from a Workers KV namespace.", { readOnlyHint: false, destructiveHint: true }, "accountId", scope.accountId, {
1893
- namespaceId: NAMESPACE_ID_PROPERTY,
1894
- keys: {
1895
- type: "array",
1896
- minItems: 1,
1897
- maxItems: 10_000,
1898
- items: { type: "string", minLength: 1, maxLength: 512 },
1899
- description: "Key names to delete, up to Cloudflare's 10,000-key bulk limit.",
1900
- }
1901
- }, ["namespaceId", "keys"], KV_BULK_RESULT_SCHEMA, async (args, ctx) => {
1902
- const { result } = await callCloudflare(send, {
1903
- method: "POST",
1904
- path: `/accounts/${encodeURIComponent(accountArg(args))}/storage/kv/namespaces/${encodeURIComponent(requireString(args, "namespaceId"))}/bulk/delete`,
1905
- body: args["keys"],
1906
- }, ctx);
1907
- return projectKvBulkResult(result);
1908
- }),
1909
1721
  cfTool("list_r2_buckets", "List R2 buckets in an account, with location and storage class.", readOnly, "accountId", scope.accountId, {
1910
1722
  nameContains: {
1911
1723
  type: "string",
@@ -1919,7 +1731,7 @@ function buildTools(scope, authentication) {
1919
1731
  },
1920
1732
  cursor: CURSOR_INPUT_PROPERTY,
1921
1733
  jurisdiction: R2_JURISDICTION_PROPERTY,
1922
- raw: RAW_INPUT_PROPERTY
1734
+ raw: RAW_INPUT_PROPERTY,
1923
1735
  }, [], {
1924
1736
  type: "object",
1925
1737
  properties: {
@@ -1954,72 +1766,12 @@ function buildTools(scope, authentication) {
1954
1766
  }),
1955
1767
  cfTool("get_r2_bucket", "Get one R2 bucket's location, jurisdiction, storage class, and creation time.", readOnly, "accountId", scope.accountId, {
1956
1768
  bucketName: R2_BUCKET_NAME_PROPERTY,
1957
- jurisdiction: R2_JURISDICTION_PROPERTY
1769
+ jurisdiction: R2_JURISDICTION_PROPERTY,
1958
1770
  }, ["bucketName"], R2_BUCKET_SCHEMA, getResult(send, (args) => ({
1959
1771
  method: "GET",
1960
1772
  path: `/accounts/${encodeURIComponent(accountArg(args))}/r2/buckets/${encodeURIComponent(requireString(args, "bucketName"))}`,
1961
1773
  headers: r2Headers(args),
1962
1774
  }), projectR2Bucket)),
1963
- cfTool("create_r2_bucket", "Create an R2 bucket with an optional location hint and default storage class.", { readOnlyHint: false }, "accountId", scope.accountId, {
1964
- bucketName: R2_BUCKET_NAME_PROPERTY,
1965
- jurisdiction: R2_JURISDICTION_PROPERTY,
1966
- locationHint: {
1967
- type: "string",
1968
- enum: ["apac", "eeur", "enam", "weur", "wnam", "oc"],
1969
- description: "Optional placement hint for the new bucket.",
1970
- },
1971
- storageClass: {
1972
- type: "string",
1973
- enum: ["Standard", "InfrequentAccess"],
1974
- description: "Default storage class for new objects.",
1975
- }
1976
- }, ["bucketName"], R2_BUCKET_SCHEMA, async (args, ctx) => {
1977
- const { result } = await callCloudflare(send, {
1978
- method: "POST",
1979
- path: `/accounts/${encodeURIComponent(accountArg(args))}/r2/buckets`,
1980
- headers: r2Headers(args),
1981
- body: compact({
1982
- name: requireString(args, "bucketName"),
1983
- locationHint: args["locationHint"],
1984
- storageClass: args["storageClass"],
1985
- }),
1986
- }, ctx);
1987
- return projectR2Bucket(result);
1988
- }),
1989
- cfTool("update_r2_bucket", "Change the default storage class used for newly uploaded objects in an R2 bucket.", { readOnlyHint: false, destructiveHint: true }, "accountId", scope.accountId, {
1990
- bucketName: R2_BUCKET_NAME_PROPERTY,
1991
- jurisdiction: R2_JURISDICTION_PROPERTY,
1992
- storageClass: {
1993
- type: "string",
1994
- enum: ["Standard", "InfrequentAccess"],
1995
- description: "New default storage class for future uploads.",
1996
- }
1997
- }, ["bucketName", "storageClass"], R2_BUCKET_SCHEMA, async (args, ctx) => {
1998
- const { result } = await callCloudflare(send, {
1999
- method: "PATCH",
2000
- path: `/accounts/${encodeURIComponent(accountArg(args))}/r2/buckets/${encodeURIComponent(requireString(args, "bucketName"))}`,
2001
- headers: {
2002
- ...r2Headers(args),
2003
- "cf-r2-storage-class": String(args["storageClass"]),
2004
- },
2005
- }, ctx);
2006
- return projectR2Bucket(result);
2007
- }),
2008
- cfTool("delete_r2_bucket", "Permanently delete an empty R2 bucket and all of its configuration. Cloudflare refuses non-empty buckets.", { readOnlyHint: false, destructiveHint: true }, "accountId", scope.accountId, {
2009
- bucketName: R2_BUCKET_NAME_PROPERTY,
2010
- jurisdiction: R2_JURISDICTION_PROPERTY
2011
- }, ["bucketName"], {
2012
- type: "object",
2013
- properties: {
2014
- deleted: { type: "boolean" },
2015
- bucketName: { type: "string" },
2016
- },
2017
- required: ["deleted", "bucketName"],
2018
- }, deleteAck(send, "bucketName", (args) => requireString(args, "bucketName"), (args, bucketName) => ({
2019
- method: "DELETE",
2020
- path: `/accounts/${encodeURIComponent(accountArg(args))}/r2/buckets/${encodeURIComponent(bucketName)}`,
2021
- headers: r2Headers(args),
2022
- }))),
2023
1775
  cfTool("list_r2_objects", "List object keys and metadata in an R2 bucket by prefix, with delimiter grouping and cursor pagination.", readOnly, "accountId", scope.accountId, {
2024
1776
  bucketName: R2_BUCKET_NAME_PROPERTY,
2025
1777
  jurisdiction: R2_JURISDICTION_PROPERTY,
@@ -2043,7 +1795,7 @@ function buildTools(scope, authentication) {
2043
1795
  maximum: 1000,
2044
1796
  description: "Objects per request, 1 to 1000.",
2045
1797
  },
2046
- cursor: CURSOR_INPUT_PROPERTY
1798
+ cursor: CURSOR_INPUT_PROPERTY,
2047
1799
  }, ["bucketName"], {
2048
1800
  type: "object",
2049
1801
  properties: {
@@ -2069,33 +1821,11 @@ function buildTools(scope, authentication) {
2069
1821
  const cursor = resultInfo?.cursor;
2070
1822
  return {
2071
1823
  objects: asArray(result).map(projectR2Object),
2072
- ...(Array.isArray(resultInfo?.delimited)
2073
- ? { commonPrefixes: resultInfo.delimited }
2074
- : {}),
1824
+ ...(Array.isArray(resultInfo?.delimited) ? { commonPrefixes: resultInfo.delimited } : {}),
2075
1825
  ...cursorResult(cursor),
2076
1826
  truncated: resultInfo?.is_truncated === true,
2077
1827
  };
2078
1828
  }),
2079
- cfTool("delete_r2_object", "Permanently delete one object from an R2 bucket by key.", { readOnlyHint: false, destructiveHint: true }, "accountId", scope.accountId, {
2080
- bucketName: R2_BUCKET_NAME_PROPERTY,
2081
- jurisdiction: R2_JURISDICTION_PROPERTY,
2082
- objectKey: {
2083
- type: "string",
2084
- minLength: 1,
2085
- description: "Exact object key. Slashes are preserved as path separators.",
2086
- }
2087
- }, ["bucketName", "objectKey"], {
2088
- type: "object",
2089
- properties: {
2090
- deleted: { type: "boolean" },
2091
- objectKey: { type: "string" },
2092
- },
2093
- required: ["deleted", "objectKey"],
2094
- }, deleteAck(send, "objectKey", (args) => requireString(args, "objectKey"), (args, objectKey) => ({
2095
- method: "DELETE",
2096
- path: `/accounts/${encodeURIComponent(accountArg(args))}/r2/buckets/${encodeURIComponent(requireString(args, "bucketName"))}/objects/${encodeObjectKey(objectKey)}`,
2097
- headers: r2Headers(args),
2098
- }))),
2099
1829
  // No `get_r2_metrics`, `set_r2_cors`, or `delete_r2_cors` on purpose (#350).
2100
1830
  // A named tool is a permanent line item in every deployment's catalog, and
2101
1831
  // these lost the comparison against the escape hatches: `get_r2_metrics`
@@ -2110,7 +1840,7 @@ function buildTools(scope, authentication) {
2110
1840
  // PUT/DELETE /accounts/{accountId}/r2/buckets/{bucketName}/cors.
2111
1841
  cfTool("get_r2_cors", "Get the browser CORS rules configured on an R2 bucket.", readOnly, "accountId", scope.accountId, {
2112
1842
  bucketName: R2_BUCKET_NAME_PROPERTY,
2113
- jurisdiction: R2_JURISDICTION_PROPERTY
1843
+ jurisdiction: R2_JURISDICTION_PROPERTY,
2114
1844
  }, ["bucketName"], R2_CORS_SCHEMA, getResult(send, (args) => ({
2115
1845
  method: "GET",
2116
1846
  path: `/accounts/${encodeURIComponent(accountArg(args))}/r2/buckets/${encodeURIComponent(requireString(args, "bucketName"))}/cors`,
@@ -2118,7 +1848,7 @@ function buildTools(scope, authentication) {
2118
1848
  }), projectR2Cors)),
2119
1849
  cfTool("list_pages_projects", "List Cloudflare Pages projects in an account, with their production branch and latest deployment.", readOnly, "accountId", scope.accountId, {
2120
1850
  ...pagingInputProperties(1, 100, { bounds: "undocumented" }),
2121
- raw: RAW_INPUT_PROPERTY
1851
+ raw: RAW_INPUT_PROPERTY,
2122
1852
  }, [], listOutputSchema("projects", PAGES_PROJECT_SCHEMA), async (args, ctx) => {
2123
1853
  const { result, resultInfo } = await callCloudflare(send, {
2124
1854
  method: "GET",
@@ -2132,11 +1862,11 @@ function buildTools(scope, authentication) {
2132
1862
  }),
2133
1863
  cfTool("get_pages_project", "Get one Pages project, including build configuration, deployment configuration, domains, and latest deployment.", readOnly, "accountId", scope.accountId, {
2134
1864
  projectName: PROJECT_NAME_PROPERTY,
2135
- raw: RAW_INPUT_PROPERTY
1865
+ raw: RAW_INPUT_PROPERTY,
2136
1866
  }, ["projectName"], PAGES_PROJECT_SCHEMA, getResult(send, (args) => ({
2137
1867
  method: "GET",
2138
1868
  path: `/accounts/${encodeURIComponent(accountArg(args))}/pages/projects/${encodeURIComponent(requireString(args, "projectName"))}`,
2139
- }), (result, args) => args["raw"] === true ? result : projectPagesProject(result))),
1869
+ }), (result, args) => (args["raw"] === true ? result : projectPagesProject(result)))),
2140
1870
  cfTool("list_pages_deployments", "List production and preview deployments for a Pages project.", readOnly, "accountId", scope.accountId, {
2141
1871
  projectName: PROJECT_NAME_PROPERTY,
2142
1872
  env: {
@@ -2144,7 +1874,7 @@ function buildTools(scope, authentication) {
2144
1874
  enum: ["production", "preview"],
2145
1875
  description: "Optional deployment environment filter.",
2146
1876
  },
2147
- ...pagingInputProperties(1, 100, { bounds: "undocumented" })
1877
+ ...pagingInputProperties(1, 100, { bounds: "undocumented" }),
2148
1878
  }, ["projectName"], listOutputSchema("deployments", PAGES_DEPLOYMENT_SCHEMA), async (args, ctx) => {
2149
1879
  const { result, resultInfo } = await callCloudflare(send, {
2150
1880
  method: "GET",
@@ -2163,40 +1893,13 @@ function buildTools(scope, authentication) {
2163
1893
  cfTool("get_pages_deployment", "Get one Pages deployment including its environment, URLs, stages, source, and build configuration.", readOnly, "accountId", scope.accountId, {
2164
1894
  projectName: PROJECT_NAME_PROPERTY,
2165
1895
  deploymentId: DEPLOYMENT_ID_PROPERTY,
2166
- raw: RAW_INPUT_PROPERTY
1896
+ raw: RAW_INPUT_PROPERTY,
2167
1897
  }, ["projectName", "deploymentId"], PAGES_DEPLOYMENT_SCHEMA, getResult(send, (args) => ({
2168
1898
  method: "GET",
2169
1899
  path: `/accounts/${encodeURIComponent(accountArg(args))}/pages/projects/${encodeURIComponent(requireString(args, "projectName"))}/deployments/${encodeURIComponent(requireString(args, "deploymentId"))}`,
2170
- }), (result, args) => args["raw"] === true ? result : projectPagesDeployment(result))),
2171
- cfTool("retry_pages_deployment", "Retry a failed or cancelled Pages deployment using its existing source and build configuration.", { readOnlyHint: false }, "accountId", scope.accountId, {
2172
- projectName: PAGES_PROJECT_NAME_PROPERTY,
2173
- deploymentId: RETRY_DEPLOYMENT_ID_PROPERTY
2174
- }, ["projectName", "deploymentId"], PAGES_DEPLOYMENT_SCHEMA, async (args, ctx) => {
2175
- const { result } = await callCloudflare(send, {
2176
- method: "POST",
2177
- path: `/accounts/${encodeURIComponent(accountArg(args))}/pages/projects/${encodeURIComponent(requireString(args, "projectName"))}/deployments/${encodeURIComponent(requireString(args, "deploymentId"))}/retry`,
2178
- }, ctx);
2179
- return projectPagesDeployment(result);
2180
- }),
2181
- cfTool("rollback_pages_deployment", "Promote a previous Pages deployment to production, replacing the currently served production deployment.", { readOnlyHint: false, destructiveHint: true }, "accountId", scope.accountId, {
2182
- projectName: PAGES_PROJECT_NAME_PROPERTY,
2183
- deploymentId: ROLLBACK_DEPLOYMENT_ID_PROPERTY
2184
- }, ["projectName", "deploymentId"], PAGES_DEPLOYMENT_SCHEMA, async (args, ctx) => {
2185
- const { result } = await callCloudflare(send, {
2186
- method: "POST",
2187
- path: `/accounts/${encodeURIComponent(accountArg(args))}/pages/projects/${encodeURIComponent(requireString(args, "projectName"))}/deployments/${encodeURIComponent(requireString(args, "deploymentId"))}/rollback`,
2188
- }, ctx);
2189
- return projectPagesDeployment(result);
2190
- }),
2191
- cfTool("delete_pages_deployment", "Permanently delete a Pages deployment and its immutable deployment URL.", { readOnlyHint: false, destructiveHint: true }, "accountId", scope.accountId, {
2192
- projectName: PAGES_PROJECT_NAME_PROPERTY,
2193
- deploymentId: DELETE_DEPLOYMENT_ID_PROPERTY
2194
- }, ["projectName", "deploymentId"], { type: "object", properties: { deleted: { type: "boolean" }, deploymentId: { type: "string" } }, required: ["deleted", "deploymentId"] }, deleteAck(send, "deploymentId", (args) => requireString(args, "deploymentId"), (args, deploymentId) => ({
2195
- method: "DELETE",
2196
- path: `/accounts/${encodeURIComponent(accountArg(args))}/pages/projects/${encodeURIComponent(requireString(args, "projectName"))}/deployments/${encodeURIComponent(deploymentId)}`,
2197
- }))),
1900
+ }), (result, args) => (args["raw"] === true ? result : projectPagesDeployment(result)))),
2198
1901
  cfTool("list_pages_domains", "List custom domains attached to a Pages project and their validation status.", readOnly, "accountId", scope.accountId, {
2199
- projectName: PAGES_PROJECT_NAME_PROPERTY
1902
+ projectName: PAGES_PROJECT_NAME_PROPERTY,
2200
1903
  }, ["projectName"], listOutputSchema("domains", PAGES_DOMAIN_SCHEMA), async (args, ctx) => {
2201
1904
  const { result } = await callCloudflare(send, {
2202
1905
  method: "GET",
@@ -2204,275 +1907,6 @@ function buildTools(scope, authentication) {
2204
1907
  }, ctx);
2205
1908
  return { domains: asArray(result).map(projectPagesDomain) };
2206
1909
  }),
2207
- cfTool("add_pages_domain", "Attach a custom domain to a Pages project. DNS ownership and validation still apply.", { readOnlyHint: false }, "accountId", scope.accountId, {
2208
- projectName: PAGES_PROJECT_NAME_PROPERTY,
2209
- domain: { type: "string", minLength: 1, description: "Fully qualified custom domain to attach." }
2210
- }, ["projectName", "domain"], PAGES_DOMAIN_SCHEMA, async (args, ctx) => {
2211
- const { result } = await callCloudflare(send, {
2212
- method: "POST",
2213
- path: `/accounts/${encodeURIComponent(accountArg(args))}/pages/projects/${encodeURIComponent(requireString(args, "projectName"))}/domains`,
2214
- body: { name: requireString(args, "domain") },
2215
- }, ctx);
2216
- return projectPagesDomain(result);
2217
- }),
2218
- cfTool("delete_pages_domain", "Detach a custom domain from a Pages project.", { readOnlyHint: false, destructiveHint: true }, "accountId", scope.accountId, {
2219
- projectName: PAGES_PROJECT_NAME_PROPERTY,
2220
- domain: { type: "string", minLength: 1, description: "Custom domain to detach." }
2221
- }, ["projectName", "domain"], { type: "object", properties: { deleted: { type: "boolean" }, domain: { type: "string" } }, required: ["deleted", "domain"] }, deleteAck(send, "domain", (args) => requireString(args, "domain"), (args, domain) => ({
2222
- method: "DELETE",
2223
- path: `/accounts/${encodeURIComponent(accountArg(args))}/pages/projects/${encodeURIComponent(requireString(args, "projectName"))}/domains/${encodeURIComponent(domain)}`,
2224
- }))),
2225
- cfTool("purge_pages_build_cache", "Clear a Pages project's build cache so its next deployment rebuilds dependencies and artifacts from scratch.", { readOnlyHint: false, destructiveHint: true }, "accountId", scope.accountId, {
2226
- projectName: PAGES_PROJECT_NAME_PROPERTY
2227
- }, ["projectName"], { type: "object", properties: { purged: { type: "boolean" } }, required: ["purged"] }, async (args, ctx) => {
2228
- await callCloudflare(send, {
2229
- method: "POST",
2230
- path: `/accounts/${encodeURIComponent(accountArg(args))}/pages/projects/${encodeURIComponent(requireString(args, "projectName"))}/purge_build_cache`,
2231
- }, ctx);
2232
- return { purged: true };
2233
- }),
2234
- cfTool("delete_pages_project", "Permanently delete a Pages project, its deployments, and project configuration.", { readOnlyHint: false, destructiveHint: true }, "accountId", scope.accountId, {
2235
- projectName: DELETE_PROJECT_NAME_PROPERTY
2236
- }, ["projectName"], { type: "object", properties: { deleted: { type: "boolean" }, projectName: { type: "string" } }, required: ["deleted", "projectName"] }, deleteAck(send, "projectName", (args) => requireString(args, "projectName"), (args, projectName) => ({
2237
- method: "DELETE",
2238
- path: `/accounts/${encodeURIComponent(accountArg(args))}/pages/projects/${encodeURIComponent(projectName)}`,
2239
- }))),
2240
- cfTool(
2241
- // Additive: brings a record into being and destroys nothing, so
2242
- // `destructiveHint` stays unset. `readOnlyHint: false` already routes it
2243
- // through call_destructive_tool.
2244
- "create_dns_record", "Create a content-based DNS record in a zone; the type enum lists the creatable types. Check list_dns_records first — Cloudflare rejects a duplicate rather than replacing it. Structured types like SRV and CAA are readable but not creatable.", { readOnlyHint: false }, "zoneId", scope.zoneId, {
2245
- type: {
2246
- type: "string",
2247
- enum: [...CLOUDFLARE_CONTENT_DNS_RECORD_TYPES],
2248
- description: "Record type.",
2249
- },
2250
- name: {
2251
- type: "string",
2252
- description: "Record name. Use the apex domain for the root, or a fully qualified subdomain, e.g. www.example.com.",
2253
- },
2254
- content: {
2255
- type: "string",
2256
- description: "Record value: an IPv4 address for A, IPv6 for AAAA, a hostname for CNAME/MX/NS, or the text body for TXT.",
2257
- },
2258
- ttl: {
2259
- type: "integer",
2260
- minimum: 1,
2261
- maximum: 86400,
2262
- description: "Time to live in seconds. 1 means automatic, which is what a proxied record must use; any other value must be at least 60 (30 on Enterprise zones). Defaults to 1.",
2263
- },
2264
- proxied: {
2265
- type: "boolean",
2266
- description: "Route through Cloudflare's proxy. Only A, AAAA, and CNAME records are proxiable. Defaults to false.",
2267
- },
2268
- priority: {
2269
- type: "integer",
2270
- minimum: 0,
2271
- maximum: 65535,
2272
- description: "Mail-server preference. MX records only.",
2273
- },
2274
- comment: {
2275
- type: "string",
2276
- description: "Operator-facing note stored with the record.",
2277
- },
2278
- tags: {
2279
- type: "array",
2280
- items: { type: "string" },
2281
- description: "Custom tags, available on paid plans.",
2282
- }
2283
- }, ["type", "name", "content"], DNS_RECORD_SCHEMA, async (args, ctx) => {
2284
- const { result } = await callCloudflare(send, {
2285
- method: "POST",
2286
- path: `/zones/${encodeURIComponent(zoneArg(args))}/dns_records`,
2287
- body: compact({
2288
- type: args["type"],
2289
- name: args["name"],
2290
- content: args["content"],
2291
- ttl: optionalNumber(args, "ttl") ?? 1,
2292
- proxied: args["proxied"],
2293
- priority: args["priority"],
2294
- comment: args["comment"],
2295
- tags: args["tags"],
2296
- }),
2297
- }, ctx);
2298
- return projectDnsRecord(result);
2299
- }),
2300
- cfTool(
2301
- // Destructive: it overwrites what a record already resolves to.
2302
- "update_dns_record", "Update fields on an existing DNS record. Only the supplied fields change; everything else keeps its current value. Changing content on a live record repoints traffic immediately.", { readOnlyHint: false, destructiveHint: true }, "zoneId", scope.zoneId, {
2303
- recordId: RECORD_ID_PROPERTY,
2304
- type: {
2305
- type: "string",
2306
- enum: [...CLOUDFLARE_CONTENT_DNS_RECORD_TYPES],
2307
- description: "Record type. Send it whenever content changes; Cloudflare treats type and content as a pair.",
2308
- },
2309
- name: { type: "string", description: "Fully qualified record name." },
2310
- content: { type: "string", description: "New record value." },
2311
- ttl: {
2312
- type: "integer",
2313
- minimum: 1,
2314
- maximum: 86400,
2315
- description: "Seconds; 1 means automatic, otherwise at least 60 (30 on Enterprise zones).",
2316
- },
2317
- proxied: {
2318
- type: "boolean",
2319
- description: "Route through Cloudflare's proxy. Only A, AAAA, and CNAME records are proxiable, and a proxied record must use ttl 1.",
2320
- },
2321
- priority: {
2322
- type: "integer",
2323
- minimum: 0,
2324
- maximum: 65535,
2325
- description: "Mail-server preference. MX records only.",
2326
- },
2327
- comment: {
2328
- type: "string",
2329
- description: "Operator-facing note stored with the record. Replaces the existing note.",
2330
- },
2331
- tags: {
2332
- type: "array",
2333
- items: { type: "string" },
2334
- description: "Custom tags, available on paid plans. Replaces the existing tag set rather than adding to it.",
2335
- }
2336
- }, ["recordId"], DNS_RECORD_SCHEMA, async (args, ctx) => {
2337
- const body = {};
2338
- for (const key of [
2339
- "type",
2340
- "name",
2341
- "content",
2342
- "ttl",
2343
- "proxied",
2344
- "priority",
2345
- "comment",
2346
- "tags",
2347
- ]) {
2348
- if (args[key] !== undefined)
2349
- body[key] = args[key];
2350
- }
2351
- if (Object.keys(body).length === 0) {
2352
- throw new ConnectorCallError("invalid_args", "update_dns_record needs at least one field to change besides zoneId and recordId.", {
2353
- validation: {
2354
- issues: [
2355
- {
2356
- path: "/",
2357
- code: "anyOf",
2358
- expected: "at least one of type, name, content, ttl, proxied, priority, comment, tags",
2359
- },
2360
- ],
2361
- },
2362
- });
2363
- }
2364
- const { result } = await callCloudflare(send, {
2365
- method: "PATCH",
2366
- path: `/zones/${encodeURIComponent(zoneArg(args))}/dns_records/${encodeURIComponent(String(args["recordId"]))}`,
2367
- body,
2368
- }, ctx);
2369
- return projectDnsRecord(result);
2370
- }),
2371
- cfTool("delete_dns_record", "Delete a DNS record by id. The record stops resolving immediately and Cloudflare keeps no undo.", { readOnlyHint: false, destructiveHint: true }, "zoneId", scope.zoneId, {
2372
- recordId: RECORD_ID_PROPERTY
2373
- }, ["recordId"], {
2374
- type: "object",
2375
- properties: {
2376
- deleted: { type: "boolean" },
2377
- recordId: { type: "string" },
2378
- },
2379
- required: ["deleted", "recordId"],
2380
- }, deleteAck(send, "recordId", (args) => String(args["recordId"]), (args, recordId) => ({
2381
- method: "DELETE",
2382
- path: `/zones/${encodeURIComponent(zoneArg(args))}/dns_records/${encodeURIComponent(recordId)}`,
2383
- }))),
2384
- cfTool("purge_cache", "Purge Cloudflare's edge cache for a zone. Prefer files, tags, hosts, or prefixes; everything discards the entire zone cache and sends every subsequent request to the origin until the cache refills.", { readOnlyHint: false, destructiveHint: true }, "zoneId", scope.zoneId, {
2385
- everything: {
2386
- type: "boolean",
2387
- description: "Purge the entire zone cache. Mutually exclusive with the targeted options below, and a real load event for the origin.",
2388
- },
2389
- files: {
2390
- type: "array",
2391
- items: { type: "string" },
2392
- minItems: 1,
2393
- maxItems: 100,
2394
- description: "Absolute URLs to purge, e.g. https://example.com/style.css. Up to 100 per request (500 on Enterprise).",
2395
- },
2396
- tags: {
2397
- type: "array",
2398
- items: { type: "string" },
2399
- minItems: 1,
2400
- maxItems: 100,
2401
- description: "Cache-Tag values to purge. Up to 100 per request; available on every plan.",
2402
- },
2403
- hosts: {
2404
- type: "array",
2405
- items: { type: "string" },
2406
- minItems: 1,
2407
- maxItems: 100,
2408
- description: "Hostnames to purge. Up to 100 per request; available on every plan.",
2409
- },
2410
- prefixes: {
2411
- type: "array",
2412
- items: { type: "string" },
2413
- minItems: 1,
2414
- maxItems: 100,
2415
- description: "URL prefixes to purge, e.g. example.com/assets. Up to 100 per request; available on every plan.",
2416
- }
2417
- }, [], {
2418
- type: "object",
2419
- properties: {
2420
- purged: { type: "boolean" },
2421
- zoneId: { type: "string" },
2422
- scope: {
2423
- type: "string",
2424
- description: "Which variant ran: everything, files, tags, hosts, or prefixes.",
2425
- },
2426
- },
2427
- required: ["purged", "zoneId", "scope"],
2428
- }, async (args, ctx) => {
2429
- const zoneId = zoneArg(args);
2430
- const targeted = ["files", "tags", "hosts", "prefixes"].filter((key) => Array.isArray(args[key]) && args[key].length > 0);
2431
- const everything = args["everything"] === true;
2432
- // One variant per call is this connection's contract, not a
2433
- // documented API restriction: Cloudflare's schema models the body as
2434
- // `anyOf`, which does not forbid combining. Refusing locally gives an
2435
- // agent `invalid_args` naming the conflict instead of a purge whose
2436
- // actual scope is ambiguous. Combined purging would be a deliberate
2437
- // change here.
2438
- if (everything && targeted.length > 0) {
2439
- throw new ConnectorCallError("invalid_args", "purge_cache takes either everything: true or one targeted list, never both.", {
2440
- validation: {
2441
- issues: [
2442
- {
2443
- path: "/everything",
2444
- code: "oneOf",
2445
- expected: "everything: true alone, or exactly one of files, tags, hosts, prefixes",
2446
- },
2447
- ],
2448
- },
2449
- });
2450
- }
2451
- if (!everything && targeted.length !== 1) {
2452
- throw new ConnectorCallError("invalid_args", targeted.length === 0
2453
- ? "purge_cache needs everything: true or one of files, tags, hosts, or prefixes."
2454
- : `purge_cache takes exactly one targeted list; received ${targeted.join(", ")}.`, {
2455
- validation: {
2456
- issues: [
2457
- {
2458
- path: "/",
2459
- code: "oneOf",
2460
- expected: "everything: true, or exactly one of files, tags, hosts, prefixes",
2461
- },
2462
- ],
2463
- },
2464
- });
2465
- }
2466
- const variant = everything ? "everything" : targeted[0];
2467
- await callCloudflare(send, {
2468
- method: "POST",
2469
- path: `/zones/${encodeURIComponent(zoneId)}/purge_cache`,
2470
- body: everything
2471
- ? { purge_everything: true }
2472
- : { [variant]: args[variant] },
2473
- }, ctx);
2474
- return { purged: true, zoneId, scope: variant };
2475
- }),
2476
1910
  ];
2477
1911
  return tools;
2478
1912
  }
@@ -2485,66 +1919,43 @@ function apiUsageGuide(purpose, scope, instructions, authentication) {
2485
1919
  ? `It defaults to account \`${scope.accountId}\`; omit \`accountId\` unless the request names a different account.`
2486
1920
  : "It declares no default account. `list_accounts` supplies the `accountId` the Workers, KV, R2, and Pages tools need.";
2487
1921
  const authenticationLine = authentication === "apiToken"
2488
- ? "The API token is operator-managed and scoped by permission. An `auth_required` failure means the token is missing, invalid, or lacks that call's permission. Call `verify_api_token` first."
2489
- : "The Global API Key and account email are operator-managed. The key has the same access as its Cloudflare user. An `auth_required` failure means one field is missing, the pair is invalid, or the user lacks access. Call `verify_global_api_key` first.";
1922
+ ? "The API token is operator-managed and scoped by permission. An `auth_required` failure means the token is missing or invalid. A `provider_permission_denied` failure asks an administrator to grant resource permission and token scope. Call `verify_api_token` first."
1923
+ : "The Global API Key and account email are operator-managed. The key has the same access as its Cloudflare user. An `auth_required` failure means one field is missing or the pair is invalid. A `provider_permission_denied` failure asks an administrator to grant the user access. Call `verify_global_api_key` first.";
2490
1924
  return `# Cloudflare usage
2491
1925
 
2492
1926
  Account purpose: ${purpose}
2493
1927
 
2494
1928
  - ${zoneLine}
2495
- - ${accountLine}
2496
- - Prefer a named tool: its schema is complete, projected, and enough to call it without provider documentation. For an operation without a named tool, use \`cloudflare_api_get\` for GET, \`cloudflare_api_mutate\` for JSON POST/PUT/PATCH/DELETE, or \`cloudflare_api_upload\` for raw and multipart content. Raw tools take a path below \`/client/v4\`; their argument schemas are complete, but endpoint-specific query, header, and body fields come from Cloudflare's API reference. Use \`headers\` for endpoint-specific controls such as \`cf-r2-jurisdiction\`, \`Range\`, \`If-None-Match\`, and Cloudflare product metadata. \`Authorization\`, \`Cookie\`, \`Host\`, \`Content-Length\`, \`Content-Type\`, and \`Transfer-Encoding\` are connector-owned and refused: authentication, host, content type, and request framing are not the caller's to set.
2497
- - The raw tools cover the wider control plane without weakening routing: GET is explicitly read-only; every mutation and upload is destructive and must cross the host's approval boundary. The configured Cloudflare credential remains the hard provider-side permission boundary. Absolute URLs, traversal, and query strings embedded in \`path\` are refused locally.
2498
- - Useful raw paths include \`/accounts/{accountId}/images/v1\` (Images), \`/accounts/{accountId}/stream\` (Stream), \`/zones/{zoneId}/email/routing/rules\` (Email Routing), \`/accounts/{accountId}/d1/database\` (D1), and \`/accounts/{accountId}/queues\` (Queues). On GET, use \`responseType: "text"\` or \`"base64"\` for non-JSON content. Direct-upload endpoints can issue upload URLs; \`cloudflare_api_upload\` can also send explicit text, base64 bytes, or multipart fields/files.
2499
- - Three areas are deliberately unnamed. Read a bucket's CORS policy with \`get_r2_cors\`, then change it with \`cloudflare_api_mutate\` — \`PUT\` or \`DELETE /accounts/{accountId}/r2/buckets/{bucketName}/cors\`, rule fields per Cloudflare's reference — and read account storage totals with \`cloudflare_api_get\` at \`/accounts/{accountId}/r2/metrics\`. Zone settings are read one at a time with \`get_zone_setting\`: Cloudflare deprecated the bulk \`/zones/{zoneId}/settings\` read and published no replacement for it, so reach for the whole set through \`cloudflare_api_get\` only when one setting genuinely will not do.
2500
- - Lists paginate with \`page\` and \`perPage\` and return a \`page\` object; request the next page only when \`page.hasMore\` is true. \`list_zone_rulesets\`, \`list_r2_buckets\`, \`list_r2_objects\`, and \`list_kv_keys\` page by cursor instead: pass \`cursor\`, continue while \`nextCursor\` is present, and expect no \`page\` object. Their schemas say so too. \`list_worker_scripts\` is unpaginated.
2501
- - Results are projected to the fields that identify and describe a resource. Pass \`raw: true\` on a read when you genuinely need a field the projection drops.
2502
- - ${authenticationLine}
2503
- - A \`rate_limited\` failure carries the wait window. Cloudflare's limit is 1,200 requests per five minutes per user, counted across the dashboard and every token, so do not fan out speculatively; filter server-side with \`name\`, \`type\`, and \`content\` instead of listing everything and filtering locally.
2504
- - Named creates that only add a resource are write-routed without claiming destruction. Updates, overwrites, deletes, rollbacks, cache purges, \`cloudflare_api_mutate\`, and \`cloudflare_api_upload\` are destructive. Read current state before changing it, and prefer a targeted \`purge_cache\` over \`everything\`.
2505
- ${accountInstructions
2506
- ? `\n## Account instructions\n\n${accountInstructions}\n`
2507
- : ""}`;
1929
+ - ${accountLine}${skill.fragments.guide_0}${authenticationLine}${skill.fragments.guide_1}${accountInstructions ? `\n## ${skill.instructionsHeading}\n\n${accountInstructions}\n` : ""}`;
2508
1930
  }
2509
- export const CLOUDFLARE_MCP_VETTED_CATALOG = vettedCatalog({
2510
- reads: new Set(["search"]),
2511
- // `execute` can send any method to more than 2,500 API endpoints. Its input
2512
- // schema cannot prove a particular program is observational, so it stays on
2513
- // the approval path even when that program happens to issue only GETs.
2514
- writes: new Map([["execute", "destructive"]]),
2515
- });
1931
+ /**
1932
+ * Reviewed in #705's provider audit against https://developers.cloudflare.com/agents/model-context-protocol/cloudflare/servers-for-cloudflare/.
1933
+ * Retains the release-reviewed inventory, including names absent from today's
1934
+ * public reference. Live annotations were not reverified without credentials.
1935
+ * No schema digest is asserted without a captured schema to review.
1936
+ */
1937
+ const CLOUDFLARE_MCP_CLASSIFICATION = {
1938
+ tools: {
1939
+ "search": {
1940
+ "verdict": "read",
1941
+ "reason": "Searches the Cloudflare OpenAPI contract without executing API methods.",
1942
+ },
1943
+ "execute": {
1944
+ "verdict": "destructive",
1945
+ "reason": "Can mix HTTP methods across the Cloudflare API; no input schema proves a program only reads.",
1946
+ },
1947
+ },
1948
+ };
2516
1949
  function mcpUsageGuide(purpose, instructions) {
2517
1950
  const accountInstructions = instructions?.trim();
2518
1951
  return `# Cloudflare MCP usage
2519
1952
 
2520
- Official whole-API MCP interface: ${purpose}
2521
-
2522
- - The catalog contains \`search\` and \`execute\`. Search runs code against
2523
- Cloudflare's OpenAPI document. Execute runs code that may call any authorized
2524
- Cloudflare API endpoint.
2525
- - Use \`search\` to find the exact method, path, and fields before writing an
2526
- execute program. Do not guess an endpoint from product naming.
2527
- - Connecta routes every \`execute\` call through approval because the tool can
2528
- mix GET, POST, PUT, PATCH, and DELETE requests inside one program. The MCP
2529
- schema cannot establish that arbitrary code is read-only.
2530
- - Keep returned values small. Filter and project inside the Cloudflare MCP
2531
- program, then reduce again inside Connecta's \`execute_code\` when several
2532
- calls must be joined.
2533
- - OAuth and API-token permissions remain the provider-side boundary. An
2534
- \`auth_required\` failure needs authorization or a token with the required
2535
- Cloudflare permission.
2536
- ${accountInstructions
2537
- ? `\n## Account instructions\n\n${accountInstructions}\n`
2538
- : ""}`;
1953
+ Official whole-API MCP interface: ${purpose}${skill.fragments.guide_2}${accountInstructions ? `\n## ${skill.instructionsHeading}\n\n${accountInstructions}\n` : ""}`;
2539
1954
  }
2540
- function cloudflareMcp(id, purpose, options) {
1955
+ function cloudflareMcp(id, purpose, options, provider) {
2541
1956
  const connector = remoteMcp(id, {
2542
1957
  url: CLOUDFLARE_MCP_ENDPOINT,
2543
- ...defined({
2544
- authScope: options.authScope,
2545
- callAdmission: options.callAdmission,
2546
- maxResultBytes: options.maxResultBytes,
2547
- }),
1958
+ ...provider.connectorOptions,
2548
1959
  title: options.title ?? "Cloudflare (MCP)",
2549
1960
  description: `Cloudflare's official whole-API MCP interface: ${purpose}`,
2550
1961
  auth: withCredentialDefaults(options.auth ?? { type: "oauth" }, {
@@ -2555,13 +1966,14 @@ function cloudflareMcp(id, purpose, options) {
2555
1966
  },
2556
1967
  }),
2557
1968
  requireHttps: true,
1969
+ classify: provider.classify,
2558
1970
  usageGuide: {
2559
1971
  content: mcpUsageGuide(purpose, options.instructions),
2560
- summary: "Official whole-API MCP. Search the OpenAPI document, then approve each mixed-method execute program.",
1972
+ summary: "Official whole-API MCP. Search the OpenAPI document; execute programs always classify as writes.",
2561
1973
  required: true,
2562
1974
  },
2563
1975
  });
2564
- return withVettedCatalog(connector, CLOUDFLARE_MCP_VETTED_CATALOG);
1976
+ return connector;
2565
1977
  }
2566
1978
  function cloudflareApi(id, purpose, options) {
2567
1979
  const maxConcurrency = options.maxConcurrency ?? 6;
@@ -2595,9 +2007,7 @@ function cloudflareApi(id, purpose, options) {
2595
2007
  // so forcing the guide into context before every operation would spend
2596
2008
  // tokens on a sequence the schemas already express.
2597
2009
  },
2598
- ...(options.maxResultBytes !== undefined
2599
- ? { maxResultBytes: options.maxResultBytes }
2600
- : {}),
2010
+ ...(options.maxResultBytes !== undefined ? { maxResultBytes: options.maxResultBytes } : {}),
2601
2011
  tools: buildTools(scope, authentication),
2602
2012
  ...(authentication === "apiToken"
2603
2013
  ? {
@@ -2621,7 +2031,7 @@ function cloudflareApi(id, purpose, options) {
2621
2031
  return await testCloudflareCredential(scope.send, { method: "GET", path: "/user" }, {
2622
2032
  ...ctx,
2623
2033
  credential: {
2624
- get: async (field) => field ? values[field] ?? null : null,
2034
+ get: async (field) => (field ? (values[field] ?? null) : null),
2625
2035
  getAll: async () => values,
2626
2036
  },
2627
2037
  }, (result) => ({
@@ -2632,13 +2042,36 @@ function cloudflareApi(id, purpose, options) {
2632
2042
  }),
2633
2043
  });
2634
2044
  }
2045
+ /** The closed options cloudflare() accepts; see `assertKnownOptions`. */
2046
+ const CLOUDFLARE_OPTIONS = variants("surface", {
2047
+ api: optionsOf()({
2048
+ ...keys("title", "authScope", "purpose", "instructions", "maxResultBytes"),
2049
+ ...keys("surface", "accountId", "zoneId", "baseUrl", "authentication", "maxConcurrency"),
2050
+ credential: CREDENTIAL,
2051
+ }).shape,
2052
+ mcp: optionsOf()({
2053
+ ...PROVIDER_COMMON,
2054
+ ...keys("surface"),
2055
+ auth: REMOTE_MCP_AUTH,
2056
+ }).shape,
2057
+ }, "mcp");
2635
2058
  /** A maintained Cloudflare connection using the selected provider interface. */
2636
- export function cloudflare(id, options) {
2059
+ export const cloudflare = defineProvider({
2060
+ name: "cloudflare",
2061
+ title: "Cloudflare",
2062
+ kind: "composed",
2063
+ readme: "Cloudflare",
2064
+ bundle: { "baselineGzip": 151186, "maxGzip": 211186 },
2065
+ skill,
2066
+ options: CLOUDFLARE_OPTIONS,
2067
+ classify: CLOUDFLARE_MCP_CLASSIFICATION,
2068
+ create: cloudflareConnector,
2069
+ });
2070
+ function cloudflareConnector(id, options, provider) {
2637
2071
  const purpose = options.purpose.trim();
2638
- if (!purpose) {
2639
- throw new Error("cloudflare() requires a non-empty account purpose.");
2640
- }
2641
- return options.surface === "mcp"
2642
- ? cloudflareMcp(id, purpose, options)
2643
- : cloudflareApi(id, purpose, options);
2072
+ return options.surface === "api"
2073
+ ? cloudflareApi(id, purpose, options)
2074
+ : cloudflareMcp(id, purpose, options, provider);
2644
2075
  }
2076
+ /** @deprecated Read `cloudflare.definition.classify` instead. Kept for existing imports. */
2077
+ export const CLOUDFLARE_MCP_VETTED_CATALOG = reviewedCatalog(cloudflare.definition.classify, 'defineProvider("cloudflare")');