@zackbart/connecta 0.15.1 → 0.16.1

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 (335) hide show
  1. package/AGENTS.md +40 -20
  2. package/CHANGELOG.md +653 -0
  3. package/README.md +25 -5
  4. package/bin/connecta.mjs +44 -3
  5. package/dist/access-tokens.d.ts +0 -1
  6. package/dist/access-tokens.js +0 -1
  7. package/dist/activity.d.ts +50 -2
  8. package/dist/activity.js +34 -1
  9. package/dist/apps-shell.d.ts +0 -1
  10. package/dist/apps-shell.js +0 -1
  11. package/dist/auth/bearer.d.ts +0 -1
  12. package/dist/auth/bearer.js +0 -1
  13. package/dist/auth/clerk.d.ts +0 -1
  14. package/dist/auth/clerk.js +45 -9
  15. package/dist/auth/downstream-oauth.d.ts +0 -1
  16. package/dist/auth/downstream-oauth.js +0 -1
  17. package/dist/call-admission.d.ts +0 -1
  18. package/dist/call-admission.js +0 -1
  19. package/dist/catalog-drift.d.ts +99 -0
  20. package/dist/catalog-drift.js +251 -0
  21. package/dist/catalog-fingerprint.d.ts +0 -1
  22. package/dist/catalog-fingerprint.js +0 -1
  23. package/dist/catalog-limits.d.ts +0 -1
  24. package/dist/catalog-limits.js +0 -1
  25. package/dist/catalog-service.d.ts +0 -1
  26. package/dist/catalog-service.js +41 -5
  27. package/dist/catalog.d.ts +11 -1
  28. package/dist/catalog.js +20 -1
  29. package/dist/concurrency.d.ts +0 -1
  30. package/dist/concurrency.js +0 -1
  31. package/dist/connector-scope.d.ts +0 -1
  32. package/dist/connector-scope.js +0 -1
  33. package/dist/connectors/api.d.ts +29 -21
  34. package/dist/connectors/api.js +45 -19
  35. package/dist/connectors/guarded-fetch.d.ts +106 -0
  36. package/dist/connectors/guarded-fetch.js +289 -0
  37. package/dist/connectors/remote-mcp.d.ts +0 -1
  38. package/dist/connectors/remote-mcp.js +0 -1
  39. package/dist/credentials.d.ts +0 -1
  40. package/dist/credentials.js +0 -1
  41. package/dist/errors.d.ts +30 -3
  42. package/dist/errors.js +1 -1
  43. package/dist/execute.d.ts +0 -1
  44. package/dist/execute.js +0 -1
  45. package/dist/executor-admission.d.ts +11 -1
  46. package/dist/executor-admission.js +28 -1
  47. package/dist/executor-result.d.ts +0 -1
  48. package/dist/executor-result.js +0 -1
  49. package/dist/executors/quickjs-child.d.ts +0 -1
  50. package/dist/executors/quickjs-child.js +0 -1
  51. package/dist/executors/quickjs-protocol.d.ts +0 -1
  52. package/dist/executors/quickjs-protocol.js +0 -1
  53. package/dist/executors/quickjs-runtime.d.ts +0 -1
  54. package/dist/executors/quickjs-runtime.js +0 -1
  55. package/dist/executors/quickjs.d.ts +0 -1
  56. package/dist/executors/quickjs.js +2 -1
  57. package/dist/favicon.d.ts +0 -1
  58. package/dist/favicon.js +0 -1
  59. package/dist/index.d.ts +2 -3
  60. package/dist/index.js +24 -7
  61. package/dist/invocation.d.ts +0 -1
  62. package/dist/invocation.js +0 -1
  63. package/dist/json-schema.d.ts +0 -1
  64. package/dist/json-schema.js +0 -1
  65. package/dist/mcp-result.d.ts +0 -1
  66. package/dist/mcp-result.js +0 -1
  67. package/dist/meta-tools.d.ts +0 -1
  68. package/dist/meta-tools.js +0 -1
  69. package/dist/node.d.ts +0 -1
  70. package/dist/node.js +0 -1
  71. package/dist/operator-ui/generated.d.ts +2 -3
  72. package/dist/operator-ui/generated.js +3 -4
  73. package/dist/operator-ui/model.d.ts +10 -1
  74. package/dist/operator-ui/model.js +0 -1
  75. package/dist/operator-ui/view.d.ts +167 -0
  76. package/dist/operator-ui/view.js +270 -0
  77. package/dist/providers/cloudflare.d.ts +0 -1
  78. package/dist/providers/cloudflare.js +261 -299
  79. package/dist/providers/linear.d.ts +26 -5
  80. package/dist/providers/linear.js +32 -61
  81. package/dist/providers/mixpanel.d.ts +35 -4
  82. package/dist/providers/mixpanel.js +53 -79
  83. package/dist/providers/notion.d.ts +0 -1
  84. package/dist/providers/notion.js +87 -42
  85. package/dist/providers/stripe.d.ts +13 -1
  86. package/dist/providers/stripe.js +31 -60
  87. package/dist/registry.d.ts +34 -2
  88. package/dist/registry.js +85 -5
  89. package/dist/retired-toolkits.d.ts +0 -1
  90. package/dist/retired-toolkits.js +0 -1
  91. package/dist/routes/access-tokens.d.ts +0 -1
  92. package/dist/routes/access-tokens.js +0 -1
  93. package/dist/routes/activity.d.ts +0 -1
  94. package/dist/routes/activity.js +0 -1
  95. package/dist/routes/credentials.d.ts +0 -1
  96. package/dist/routes/credentials.js +0 -1
  97. package/dist/routes/mcp.d.ts +0 -1
  98. package/dist/routes/mcp.js +0 -1
  99. package/dist/routes/oauth.d.ts +0 -1
  100. package/dist/routes/oauth.js +0 -1
  101. package/dist/routes/shared.d.ts +2 -1
  102. package/dist/routes/shared.js +0 -1
  103. package/dist/routes/ui.d.ts +0 -1
  104. package/dist/routes/ui.js +0 -1
  105. package/dist/server.d.ts +0 -1
  106. package/dist/server.js +11 -1
  107. package/dist/skills.d.ts +0 -1
  108. package/dist/skills.js +0 -1
  109. package/dist/storage/file.d.ts +0 -1
  110. package/dist/storage/file.js +0 -1
  111. package/dist/storage/memory.d.ts +0 -1
  112. package/dist/storage/memory.js +0 -1
  113. package/dist/timeout.d.ts +0 -1
  114. package/dist/timeout.js +0 -1
  115. package/dist/tool-safety.d.ts +0 -1
  116. package/dist/tool-safety.js +0 -1
  117. package/dist/types.d.ts +45 -1
  118. package/dist/types.js +0 -1
  119. package/dist/ui.d.ts +0 -1
  120. package/dist/ui.js +13 -137
  121. package/dist/validate.d.ts +13 -15
  122. package/dist/validate.js +24 -11
  123. package/dist/version.d.ts +1 -2
  124. package/dist/version.js +1 -2
  125. package/documentation/architecture.md +186 -5
  126. package/documentation/auth.md +11 -0
  127. package/documentation/call-admission.md +142 -6
  128. package/documentation/cloudflare.md +92 -10
  129. package/documentation/code-first-exploration.md +2 -2
  130. package/documentation/code-mode.md +14 -14
  131. package/documentation/connector-guides.md +172 -5
  132. package/documentation/connectors.md +89 -0
  133. package/documentation/linear.md +20 -2
  134. package/documentation/mcp-ui-design.md +2 -2
  135. package/documentation/meta-tools.md +7 -1
  136. package/documentation/mixpanel.md +42 -9
  137. package/documentation/notion.md +23 -2
  138. package/documentation/operations.md +325 -5
  139. package/documentation/operator-ui.md +120 -5
  140. package/documentation/provider-audit.md +160 -0
  141. package/documentation/provider-conventions.md +667 -0
  142. package/documentation/request-admission.md +167 -5
  143. package/documentation/rich-output-design.md +1 -2
  144. package/documentation/stripe.md +15 -0
  145. package/documentation/upgrading.md +454 -0
  146. package/ethos.md +14 -9
  147. package/examples/worker/README.md +102 -12
  148. package/examples/worker/src/index.ts +46 -1
  149. package/examples/worker/wrangler.jsonc +14 -1
  150. package/package.json +10 -6
  151. package/templates/node/.dockerignore +10 -0
  152. package/templates/node/.env.example +40 -3
  153. package/templates/node/AGENTS.md +16 -1
  154. package/templates/node/Dockerfile +49 -0
  155. package/templates/node/README.md +117 -2
  156. package/templates/node/docker-compose.yml +39 -0
  157. package/templates/node/package.json +1 -1
  158. package/templates/node/src/file-activity.ts +182 -0
  159. package/templates/node/src/index.ts +80 -4
  160. package/assets/connecta-clay-hero.png +0 -0
  161. package/dist/access-tokens.d.ts.map +0 -1
  162. package/dist/access-tokens.js.map +0 -1
  163. package/dist/activity.d.ts.map +0 -1
  164. package/dist/activity.js.map +0 -1
  165. package/dist/apps-shell.d.ts.map +0 -1
  166. package/dist/apps-shell.js.map +0 -1
  167. package/dist/auth/bearer.d.ts.map +0 -1
  168. package/dist/auth/bearer.js.map +0 -1
  169. package/dist/auth/clerk.d.ts.map +0 -1
  170. package/dist/auth/clerk.js.map +0 -1
  171. package/dist/auth/downstream-oauth.d.ts.map +0 -1
  172. package/dist/auth/downstream-oauth.js.map +0 -1
  173. package/dist/call-admission.d.ts.map +0 -1
  174. package/dist/call-admission.js.map +0 -1
  175. package/dist/catalog-fingerprint.d.ts.map +0 -1
  176. package/dist/catalog-fingerprint.js.map +0 -1
  177. package/dist/catalog-limits.d.ts.map +0 -1
  178. package/dist/catalog-limits.js.map +0 -1
  179. package/dist/catalog-service.d.ts.map +0 -1
  180. package/dist/catalog-service.js.map +0 -1
  181. package/dist/catalog.d.ts.map +0 -1
  182. package/dist/catalog.js.map +0 -1
  183. package/dist/concurrency.d.ts.map +0 -1
  184. package/dist/concurrency.js.map +0 -1
  185. package/dist/connector-scope.d.ts.map +0 -1
  186. package/dist/connector-scope.js.map +0 -1
  187. package/dist/connectors/api.d.ts.map +0 -1
  188. package/dist/connectors/api.js.map +0 -1
  189. package/dist/connectors/remote-mcp.d.ts.map +0 -1
  190. package/dist/connectors/remote-mcp.js.map +0 -1
  191. package/dist/credentials.d.ts.map +0 -1
  192. package/dist/credentials.js.map +0 -1
  193. package/dist/errors.d.ts.map +0 -1
  194. package/dist/errors.js.map +0 -1
  195. package/dist/execute.d.ts.map +0 -1
  196. package/dist/execute.js.map +0 -1
  197. package/dist/executor-admission.d.ts.map +0 -1
  198. package/dist/executor-admission.js.map +0 -1
  199. package/dist/executor-result.d.ts.map +0 -1
  200. package/dist/executor-result.js.map +0 -1
  201. package/dist/executors/quickjs-child.d.ts.map +0 -1
  202. package/dist/executors/quickjs-child.js.map +0 -1
  203. package/dist/executors/quickjs-protocol.d.ts.map +0 -1
  204. package/dist/executors/quickjs-protocol.js.map +0 -1
  205. package/dist/executors/quickjs-runtime.d.ts.map +0 -1
  206. package/dist/executors/quickjs-runtime.js.map +0 -1
  207. package/dist/executors/quickjs.d.ts.map +0 -1
  208. package/dist/executors/quickjs.js.map +0 -1
  209. package/dist/favicon.d.ts.map +0 -1
  210. package/dist/favicon.js.map +0 -1
  211. package/dist/index.d.ts.map +0 -1
  212. package/dist/index.js.map +0 -1
  213. package/dist/invocation.d.ts.map +0 -1
  214. package/dist/invocation.js.map +0 -1
  215. package/dist/json-schema.d.ts.map +0 -1
  216. package/dist/json-schema.js.map +0 -1
  217. package/dist/mcp-result.d.ts.map +0 -1
  218. package/dist/mcp-result.js.map +0 -1
  219. package/dist/meta-tools.d.ts.map +0 -1
  220. package/dist/meta-tools.js.map +0 -1
  221. package/dist/node.d.ts.map +0 -1
  222. package/dist/node.js.map +0 -1
  223. package/dist/operator-ui/generated.d.ts.map +0 -1
  224. package/dist/operator-ui/generated.js.map +0 -1
  225. package/dist/operator-ui/model.d.ts.map +0 -1
  226. package/dist/operator-ui/model.js.map +0 -1
  227. package/dist/providers/cloudflare.d.ts.map +0 -1
  228. package/dist/providers/cloudflare.js.map +0 -1
  229. package/dist/providers/linear.d.ts.map +0 -1
  230. package/dist/providers/linear.js.map +0 -1
  231. package/dist/providers/mixpanel.d.ts.map +0 -1
  232. package/dist/providers/mixpanel.js.map +0 -1
  233. package/dist/providers/notion.d.ts.map +0 -1
  234. package/dist/providers/notion.js.map +0 -1
  235. package/dist/providers/stripe.d.ts.map +0 -1
  236. package/dist/providers/stripe.js.map +0 -1
  237. package/dist/registry.d.ts.map +0 -1
  238. package/dist/registry.js.map +0 -1
  239. package/dist/retired-toolkits.d.ts.map +0 -1
  240. package/dist/retired-toolkits.js.map +0 -1
  241. package/dist/routes/access-tokens.d.ts.map +0 -1
  242. package/dist/routes/access-tokens.js.map +0 -1
  243. package/dist/routes/activity.d.ts.map +0 -1
  244. package/dist/routes/activity.js.map +0 -1
  245. package/dist/routes/credentials.d.ts.map +0 -1
  246. package/dist/routes/credentials.js.map +0 -1
  247. package/dist/routes/mcp.d.ts.map +0 -1
  248. package/dist/routes/mcp.js.map +0 -1
  249. package/dist/routes/oauth.d.ts.map +0 -1
  250. package/dist/routes/oauth.js.map +0 -1
  251. package/dist/routes/shared.d.ts.map +0 -1
  252. package/dist/routes/shared.js.map +0 -1
  253. package/dist/routes/ui.d.ts.map +0 -1
  254. package/dist/routes/ui.js.map +0 -1
  255. package/dist/server.d.ts.map +0 -1
  256. package/dist/server.js.map +0 -1
  257. package/dist/skills.d.ts.map +0 -1
  258. package/dist/skills.js.map +0 -1
  259. package/dist/storage/file.d.ts.map +0 -1
  260. package/dist/storage/file.js.map +0 -1
  261. package/dist/storage/memory.d.ts.map +0 -1
  262. package/dist/storage/memory.js.map +0 -1
  263. package/dist/timeout.d.ts.map +0 -1
  264. package/dist/timeout.js.map +0 -1
  265. package/dist/tool-safety.d.ts.map +0 -1
  266. package/dist/tool-safety.js.map +0 -1
  267. package/dist/types.d.ts.map +0 -1
  268. package/dist/types.js.map +0 -1
  269. package/dist/ui.d.ts.map +0 -1
  270. package/dist/ui.js.map +0 -1
  271. package/dist/validate.d.ts.map +0 -1
  272. package/dist/validate.js.map +0 -1
  273. package/dist/version.d.ts.map +0 -1
  274. package/dist/version.js.map +0 -1
  275. package/examples/node/README.md +0 -52
  276. package/examples/node/src/index.ts +0 -71
  277. package/src/access-tokens.ts +0 -289
  278. package/src/activity.ts +0 -234
  279. package/src/apps-shell.ts +0 -370
  280. package/src/auth/bearer.ts +0 -61
  281. package/src/auth/clerk.ts +0 -553
  282. package/src/auth/downstream-oauth.ts +0 -575
  283. package/src/call-admission.ts +0 -521
  284. package/src/catalog-fingerprint.ts +0 -52
  285. package/src/catalog-limits.ts +0 -11
  286. package/src/catalog-service.ts +0 -1153
  287. package/src/catalog.ts +0 -790
  288. package/src/concurrency.ts +0 -39
  289. package/src/connector-scope.ts +0 -66
  290. package/src/connectors/api.ts +0 -159
  291. package/src/connectors/remote-mcp.ts +0 -1090
  292. package/src/credentials.ts +0 -428
  293. package/src/errors.ts +0 -364
  294. package/src/execute.ts +0 -1406
  295. package/src/executor-admission.ts +0 -328
  296. package/src/executor-result.ts +0 -107
  297. package/src/executors/quickjs-child.ts +0 -172
  298. package/src/executors/quickjs-protocol.ts +0 -99
  299. package/src/executors/quickjs-runtime.ts +0 -470
  300. package/src/executors/quickjs.ts +0 -776
  301. package/src/favicon.ts +0 -53
  302. package/src/index.ts +0 -666
  303. package/src/invocation.ts +0 -655
  304. package/src/json-schema.ts +0 -11
  305. package/src/mcp-result.ts +0 -43
  306. package/src/meta-tools.ts +0 -1607
  307. package/src/node.ts +0 -206
  308. package/src/operator-ui/browser.css +0 -423
  309. package/src/operator-ui/browser.ts +0 -1281
  310. package/src/operator-ui/generated.ts +0 -4
  311. package/src/operator-ui/model.ts +0 -111
  312. package/src/providers/cloudflare.ts +0 -3999
  313. package/src/providers/linear.ts +0 -302
  314. package/src/providers/mixpanel.ts +0 -229
  315. package/src/providers/notion.ts +0 -1879
  316. package/src/providers/stripe.ts +0 -307
  317. package/src/registry.ts +0 -1085
  318. package/src/retired-toolkits.ts +0 -14
  319. package/src/routes/access-tokens.ts +0 -115
  320. package/src/routes/activity.ts +0 -215
  321. package/src/routes/credentials.ts +0 -254
  322. package/src/routes/mcp.ts +0 -429
  323. package/src/routes/oauth.ts +0 -322
  324. package/src/routes/shared.ts +0 -247
  325. package/src/routes/ui.ts +0 -152
  326. package/src/server.ts +0 -195
  327. package/src/skills.ts +0 -271
  328. package/src/storage/file.ts +0 -123
  329. package/src/storage/memory.ts +0 -44
  330. package/src/timeout.ts +0 -84
  331. package/src/tool-safety.ts +0 -15
  332. package/src/types.ts +0 -523
  333. package/src/ui.ts +0 -744
  334. package/src/validate.ts +0 -387
  335. package/src/version.ts +0 -7
@@ -16,6 +16,7 @@
16
16
  * required-key list, and a declared output shape.
17
17
  */
18
18
  import { api } from "../connectors/api.js";
19
+ import { guardedFetch, } from "../connectors/guarded-fetch.js";
19
20
  import { ConnectorCallError } from "../errors.js";
20
21
  /** Cloudflare's v4 REST base. Override only for a proxy or a test double. */
21
22
  export const CLOUDFLARE_API_BASE = "https://api.cloudflare.com/client/v4";
@@ -234,14 +235,28 @@ function failureFor(status, headers, errors) {
234
235
  if (status === 400 || status === 409 || status === 422) {
235
236
  return new ConnectorCallError("invalid_args", `Cloudflare rejected the request (HTTP ${status}). ${detail}`);
236
237
  }
238
+ // Cloudflare's 404 is an honest absence: a token that may not touch a
239
+ // resource is refused with 401 or 403 above, not hidden behind a miss, so
240
+ // this is the unambiguous case `not_found` exists for — re-address, do not
241
+ // retry and do not re-authorize.
237
242
  if (status === 404) {
238
- return new ConnectorCallError("connector_call_failed", `Cloudflare found no such resource (HTTP 404). ${detail} Confirm the zone or account id with list_zones or list_accounts.`);
243
+ return new ConnectorCallError("not_found", `Cloudflare found no such resource (HTTP 404). ${detail} Confirm the zone or account id with list_zones or list_accounts.`);
239
244
  }
240
245
  if (status >= 500) {
241
246
  return new ConnectorCallError("unavailable", `Cloudflare is unavailable (HTTP ${status}). ${detail}`);
242
247
  }
243
248
  return new ConnectorCallError("connector_call_failed", `Cloudflare request failed (HTTP ${status}). ${detail}`);
244
249
  }
250
+ // --- The request path --------------------------------------------------------
251
+ /**
252
+ * The largest response this connection will read.
253
+ *
254
+ * Generous rather than tight, because `cloudflare_api_get` legitimately
255
+ * downloads Worker scripts and R2 objects. It is a ceiling on absurdity, not a
256
+ * quota: anything approaching it is already far past whatever `maxResultBytes`
257
+ * the deployment set, so the caller was never going to see it whole.
258
+ */
259
+ const CLOUDFLARE_MAX_RESPONSE_BYTES = 8 * 1024 * 1024;
245
260
  const AUTHENTICATION_CONTEXT = Symbol("cloudflareAuthentication");
246
261
  function withAuthentication(ctx, authentication) {
247
262
  return { ...ctx, [AUTHENTICATION_CONTEXT]: authentication };
@@ -262,72 +277,63 @@ async function readAuthenticationHeaders(ctx) {
262
277
  }
263
278
  return { Authorization: `Bearer ${token}` };
264
279
  }
265
- function buildUrl(base, spec) {
266
- const url = new URL(`${base.replace(/\/+$/, "")}${spec.path}`);
267
- for (const [key, value] of Object.entries(spec.query ?? {})) {
268
- if (value === undefined)
269
- continue;
270
- url.searchParams.set(key, String(value));
271
- }
272
- return url.toString();
273
- }
274
- async function fetchCloudflare(base, spec, ctx) {
275
- const authenticationHeaders = await readAuthenticationHeaders(ctx);
276
- if (spec.body !== undefined && spec.rawBody !== undefined) {
277
- throw new Error("A Cloudflare request cannot have both JSON and raw bodies.");
278
- }
279
- let response;
280
- try {
281
- response = await fetch(buildUrl(base, spec), {
282
- method: spec.method,
283
- headers: {
284
- ...authenticationHeaders,
285
- Accept: "application/json",
286
- ...(spec.body !== undefined
287
- ? { "Content-Type": "application/json" }
288
- : {}),
289
- ...Object.fromEntries(Object.entries(spec.headers ?? {}).filter((entry) => entry[1] !== undefined)),
290
- },
291
- ...(spec.body !== undefined
292
- ? { body: JSON.stringify(spec.body) }
293
- : spec.rawBody !== undefined
294
- ? { body: spec.rawBody }
295
- : {}),
296
- ...(ctx.signal ? { signal: ctx.signal } : {}),
297
- });
298
- }
299
- catch (cause) {
300
- throw new ConnectorCallError("unavailable", `Could not reach the Cloudflare API: ${cause instanceof Error ? cause.message : String(cause)}`, { cause });
301
- }
302
- return response;
280
+ /**
281
+ * The one transport every Cloudflare tool goes through.
282
+ *
283
+ * URL confinement, `ctx.signal`, redirect refusal, bounded reads, and the
284
+ * "could not reach the provider" normalization all live in the shared helper.
285
+ * What stays here is what only Cloudflare knows: which headers prove identity,
286
+ * and what a status code means once it arrives.
287
+ */
288
+ function cloudflareTransport(baseUrl) {
289
+ return guardedFetch({
290
+ provider: "Cloudflare",
291
+ baseUrl,
292
+ headers: { Accept: "application/json" },
293
+ maxResponseBytes: CLOUDFLARE_MAX_RESPONSE_BYTES,
294
+ authenticate: (ctx) => readAuthenticationHeaders(ctx),
295
+ });
303
296
  }
304
- async function callCloudflare(base, spec, ctx) {
305
- const response = await fetchCloudflare(base, spec, ctx);
306
- let envelope;
307
- try {
308
- envelope = (await response.json());
309
- }
310
- catch (cause) {
311
- // A gateway error page, not JSON: the status is the only real signal left.
312
- throw response.ok
313
- ? new ConnectorCallError("unavailable", "Cloudflare returned a non-JSON body for a successful status.", { cause })
314
- : failureFor(response.status, response.headers, []);
315
- }
316
- const errors = Array.isArray(envelope.errors) ? envelope.errors : [];
317
- if (!response.ok || envelope.success === false) {
318
- throw failureFor(response.status, response.headers, errors);
319
- }
320
- const isV4Envelope = "success" in envelope ||
321
- "result" in envelope ||
322
- "result_info" in envelope ||
323
- "messages" in envelope;
324
- return {
325
- // `/graphql` and a small number of product APIs return ordinary JSON
326
- // instead of the standard v4 envelope. Preserve that document whole so
327
- // the raw tools cover them too.
328
- result: isV4Envelope ? envelope.result : envelope,
329
- resultInfo: isV4Envelope ? envelope.result_info : undefined,
330
- };
297
+ async function callCloudflare(send, spec, ctx) {
298
+ return await send(spec, ctx, async (response) => {
299
+ let envelope;
300
+ let parseFailure;
301
+ try {
302
+ envelope = (await response.json());
303
+ }
304
+ catch (cause) {
305
+ // A transport failure is not a parse failure. The connector's byte
306
+ // ceiling fires from inside this read and is deliberately non-retryable;
307
+ // routing it through the branch below would relabel it as a retryable
308
+ // `unavailable` and tell an agent to retry a response that will exceed
309
+ // the ceiling every time.
310
+ if (cause instanceof ConnectorCallError)
311
+ throw cause;
312
+ parseFailure = cause;
313
+ }
314
+ if (envelope === undefined) {
315
+ // A gateway error page or an empty body, not an envelope: the status is
316
+ // the only real signal left.
317
+ throw response.ok
318
+ ? new ConnectorCallError("unavailable", "Cloudflare returned a non-JSON body for a successful status.", parseFailure !== undefined ? { cause: parseFailure } : {})
319
+ : failureFor(response.status, response.headers, []);
320
+ }
321
+ const errors = Array.isArray(envelope.errors) ? envelope.errors : [];
322
+ if (!response.ok || envelope.success === false) {
323
+ throw failureFor(response.status, response.headers, errors);
324
+ }
325
+ const isV4Envelope = "success" in envelope ||
326
+ "result" in envelope ||
327
+ "result_info" in envelope ||
328
+ "messages" in envelope;
329
+ return {
330
+ // `/graphql` and a small number of product APIs return ordinary JSON
331
+ // instead of the standard v4 envelope. Preserve that document whole so
332
+ // the raw tools cover them too.
333
+ result: isV4Envelope ? envelope.result : envelope,
334
+ resultInfo: isV4Envelope ? envelope.result_info : undefined,
335
+ };
336
+ });
331
337
  }
332
338
  function base64FromBytes(bytes) {
333
339
  let binary = "";
@@ -336,33 +342,34 @@ function base64FromBytes(bytes) {
336
342
  }
337
343
  return btoa(binary);
338
344
  }
339
- async function callCloudflareContent(base, spec, ctx, responseType) {
340
- const response = await fetchCloudflare(base, spec, ctx);
341
- if (!response.ok) {
342
- let errors = [];
343
- try {
344
- const envelope = (await response.clone().json());
345
- if (Array.isArray(envelope.errors))
346
- errors = envelope.errors;
345
+ async function callCloudflareContent(send, spec, ctx, responseType) {
346
+ return await send(spec, ctx, async (response) => {
347
+ if (!response.ok) {
348
+ let errors = [];
349
+ try {
350
+ const envelope = (await response.json());
351
+ if (envelope && Array.isArray(envelope.errors))
352
+ errors = envelope.errors;
353
+ }
354
+ catch {
355
+ // A raw or gateway error body has no structured detail to preserve,
356
+ // and one past the byte ceiling has none worth reporting over the
357
+ // status that already failed this call. Either way the throw below
358
+ // classifies the status, so nothing is swallowed into a success.
359
+ }
360
+ throw failureFor(response.status, response.headers, errors);
347
361
  }
348
- catch {
349
- // A raw or gateway error body has no structured detail to preserve.
362
+ const common = {
363
+ contentType: response.headers.get("content-type") ?? "application/octet-stream",
364
+ ...(response.headers.get("etag")
365
+ ? { etag: response.headers.get("etag") }
366
+ : {}),
367
+ };
368
+ if (responseType === "text") {
369
+ return { ...common, text: await response.text() };
350
370
  }
351
- throw failureFor(response.status, response.headers, errors);
352
- }
353
- const common = {
354
- contentType: response.headers.get("content-type") ?? "application/octet-stream",
355
- ...(response.headers.get("etag")
356
- ? { etag: response.headers.get("etag") }
357
- : {}),
358
- };
359
- if (responseType === "text") {
360
- return { ...common, text: await response.text() };
361
- }
362
- return {
363
- ...common,
364
- base64: base64FromBytes(new Uint8Array(await response.arrayBuffer())),
365
- };
371
+ return { ...common, base64: base64FromBytes(await response.bytes()) };
372
+ });
366
373
  }
367
374
  /**
368
375
  * Cloudflare's `result_info` reshaped into the one question an agent actually
@@ -631,8 +638,8 @@ const RAW_INPUT_PROPERTY = {
631
638
  * an out-of-range value with a 400, so each caller passes its own. Encoding
632
639
  * them in the schema turns a wasted round trip into a local repair — but only
633
640
  * where the bound is really Cloudflare's. `bounds` records who chose the
634
- * range, because `strictValidation` refuses an out-of-range `perPage` locally
635
- * and an agent deserves to know whether the wall it hit is the API's or ours:
641
+ * range, because `api()` refuses an out-of-range `perPage` locally and an
642
+ * agent deserves to know whether the wall it hit is the API's or ours:
636
643
  *
637
644
  * - `"cloudflare"` — the schema's own documented minimum and maximum.
638
645
  * - `"clamped"` — Cloudflare accepts more; this connection caps it lower.
@@ -663,6 +670,24 @@ function pagingInputProperties(minPerPage, maxPerPage, options = {}) {
663
670
  },
664
671
  };
665
672
  }
673
+ /**
674
+ * The second pagination convention, and the schemas say so.
675
+ *
676
+ * Rulesets, KV keys, and both R2 listings page by cursor rather than by page
677
+ * number, because that is how Cloudflare built them. The guide names the four,
678
+ * but an agent reading one tool's schema should not have to fetch the guide to
679
+ * learn that the `page` object it expected is not coming: absence of
680
+ * `nextCursor` is the loop condition here, and it is stated on both ends
681
+ * ([#342](https://github.com/zackbart/connecta/issues/342)).
682
+ */
683
+ const CURSOR_INPUT_PROPERTY = {
684
+ type: "string",
685
+ description: "Opaque cursor from a previous call's nextCursor. This endpoint pages by cursor, not page number.",
686
+ };
687
+ const NEXT_CURSOR_OUTPUT_PROPERTY = {
688
+ type: "string",
689
+ description: "Pass back as `cursor` to continue. Absent when the listing is complete — this is the only signal; there is no page object.",
690
+ };
666
691
  function listOutputSchema(key, item) {
667
692
  return {
668
693
  type: "object",
@@ -928,7 +953,7 @@ const OPEN_OBJECT_OUTPUT_SCHEMA = {
928
953
  };
929
954
  const QUERY_INPUT_PROPERTY = {
930
955
  type: "array",
931
- description: "Optional query parameters as name/value pairs. Each parameter name may appear once.",
956
+ description: "Query parameters as name/value pairs; each name may appear once.",
932
957
  items: {
933
958
  type: "object",
934
959
  properties: {
@@ -939,9 +964,14 @@ const QUERY_INPUT_PROPERTY = {
939
964
  additionalProperties: false,
940
965
  },
941
966
  };
967
+ // Shared by all three escape hatches, and the compact renderer inlines it
968
+ // three times over. The refused-header list is the connector's boundary rather
969
+ // than a fact the caller composes with, so it lives in the usage guide; keeping
970
+ // it here pushed cloudflare_api_upload's compact input past the 1,024-byte
971
+ // budget ([#342](https://github.com/zackbart/connecta/issues/342)).
942
972
  const HEADERS_INPUT_PROPERTY = {
943
973
  type: "array",
944
- description: "Optional provider headers as name/value pairs, for example cf-r2-jurisdiction, Range, If-None-Match, or Cloudflare product metadata. Authorization, Cookie, Host, Content-Length, Content-Type, and Transfer-Encoding are connector-owned and refused.",
974
+ description: "Endpoint headers as name/value pairs, e.g. cf-r2-jurisdiction or Range. Connector-owned headers are refused.",
945
975
  items: {
946
976
  type: "object",
947
977
  properties: {
@@ -988,7 +1018,7 @@ const R2_OBJECT_SCHEMA = {
988
1018
  required: ["key"],
989
1019
  };
990
1020
  function buildTools(scope, authentication) {
991
- const { base } = scope;
1021
+ const { send } = scope;
992
1022
  const zoneArg = (args) => requireScope(args["zoneId"], scope.zoneId, "zoneId");
993
1023
  const accountArg = (args) => requireScope(args["accountId"], scope.accountId, "accountId");
994
1024
  const readOnly = { readOnlyHint: true, destructiveHint: false };
@@ -1018,7 +1048,7 @@ function buildTools(scope, authentication) {
1018
1048
  required: ["status"],
1019
1049
  },
1020
1050
  handler: async (_args, ctx) => {
1021
- const { result } = await callCloudflare(base, { method: "GET", path: "/user/tokens/verify" }, ctx);
1051
+ const { result } = await callCloudflare(send, { method: "GET", path: "/user/tokens/verify" }, ctx);
1022
1052
  const token = asRecord(result);
1023
1053
  return {
1024
1054
  id: token["id"],
@@ -1055,14 +1085,14 @@ function buildTools(scope, authentication) {
1055
1085
  required: ["email", "status"],
1056
1086
  },
1057
1087
  handler: async (_args, ctx) => {
1058
- const { result } = await callCloudflare(base, { method: "GET", path: "/user" }, ctx);
1088
+ const { result } = await callCloudflare(send, { method: "GET", path: "/user" }, ctx);
1059
1089
  const user = asRecord(result);
1060
1090
  return { id: user["id"], email: user["email"], status: "active" };
1061
1091
  },
1062
1092
  },
1063
1093
  {
1064
1094
  name: "cloudflare_api_get",
1065
- description: "Call any GET endpoint under Cloudflare's v4 API with this connector's credential. Use a named tool when one exists; use this read-only escape hatch for Images, Stream, Email Routing, D1, Queues, Access, Tunnels, Analytics, and newer product endpoints the curated surface does not yet name.",
1095
+ description: "Call any GET endpoint under Cloudflare's v4 API with this connector's credential. Prefer a named tool when one exists; this read-only hatch covers the products the named surface does not reach, such as Images, Stream, D1, and Queues.",
1066
1096
  annotations: readOnly,
1067
1097
  inputSchema: {
1068
1098
  type: "object",
@@ -1111,9 +1141,9 @@ function buildTools(scope, authentication) {
1111
1141
  ...(headers !== undefined ? { headers } : {}),
1112
1142
  };
1113
1143
  if (responseType === "text" || responseType === "base64") {
1114
- return await callCloudflareContent(base, spec, ctx, responseType);
1144
+ return await callCloudflareContent(send, spec, ctx, responseType);
1115
1145
  }
1116
- const { result, resultInfo } = await callCloudflare(base, spec, ctx);
1146
+ const { result, resultInfo } = await callCloudflare(send, spec, ctx);
1117
1147
  return {
1118
1148
  result,
1119
1149
  ...(resultInfo !== undefined ? { resultInfo } : {}),
@@ -1122,7 +1152,7 @@ function buildTools(scope, authentication) {
1122
1152
  },
1123
1153
  {
1124
1154
  name: "cloudflare_api_mutate",
1125
- description: "Call any JSON POST, PUT, PATCH, or DELETE endpoint under Cloudflare's v4 API with this connector's credential. This is the approval-gated escape hatch for managing Cloudflare products without waiting for a named tool. It does not support multipart or binary uploads.",
1155
+ description: "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.",
1126
1156
  annotations: { readOnlyHint: false, destructiveHint: true },
1127
1157
  inputSchema: {
1128
1158
  type: "object",
@@ -1164,7 +1194,7 @@ function buildTools(scope, authentication) {
1164
1194
  const method = String(args["method"]);
1165
1195
  const query = queryFromArgs(args["query"]);
1166
1196
  const headers = headersFromArgs(args["headers"]);
1167
- const { result, resultInfo } = await callCloudflare(base, {
1197
+ const { result, resultInfo } = await callCloudflare(send, {
1168
1198
  method,
1169
1199
  path: cloudflareApiPath(args["path"]),
1170
1200
  ...(query !== undefined ? { query } : {}),
@@ -1187,31 +1217,31 @@ function buildTools(scope, authentication) {
1187
1217
  method: {
1188
1218
  type: "string",
1189
1219
  enum: ["POST", "PUT"],
1190
- description: "HTTP upload method required by the Cloudflare endpoint.",
1220
+ description: "Upload method the Cloudflare endpoint requires.",
1191
1221
  },
1192
1222
  path: {
1193
1223
  type: "string",
1194
1224
  minLength: 1,
1195
- description: "Relative path below /client/v4, beginning with '/'. Do not include a query string.",
1225
+ description: "Path below /client/v4, beginning with '/'. No query string.",
1196
1226
  },
1197
1227
  query: QUERY_INPUT_PROPERTY,
1198
1228
  headers: HEADERS_INPUT_PROPERTY,
1199
1229
  contentType: {
1200
1230
  type: "string",
1201
1231
  minLength: 1,
1202
- description: "Content-Type for a raw text/base64 body. Omit for multipart because fetch supplies the boundary.",
1232
+ description: "Content-Type for a raw text or base64 body. Omit for multipart.",
1203
1233
  },
1204
1234
  textBody: {
1205
1235
  type: "string",
1206
- description: "Raw UTF-8 request body. Mutually exclusive with base64Body and multipart fields/files.",
1236
+ description: "Raw UTF-8 body. Exclusive with base64Body and fields/files.",
1207
1237
  },
1208
1238
  base64Body: {
1209
1239
  type: "string",
1210
- description: "Base64-encoded request bytes. Mutually exclusive with textBody and multipart fields/files.",
1240
+ description: "Base64-encoded body bytes. Exclusive with textBody and fields/files.",
1211
1241
  },
1212
1242
  fields: {
1213
1243
  type: "array",
1214
- description: "String fields for a multipart/form-data request.",
1244
+ description: "String fields of a multipart/form-data request.",
1215
1245
  items: {
1216
1246
  type: "object",
1217
1247
  properties: {
@@ -1226,7 +1256,7 @@ function buildTools(scope, authentication) {
1226
1256
  },
1227
1257
  files: {
1228
1258
  type: "array",
1229
- description: "File parts for multipart/form-data. Each file needs exactly one of text or base64.",
1259
+ description: "Multipart file parts. Each needs exactly one of text or base64.",
1230
1260
  items: {
1231
1261
  type: "object",
1232
1262
  properties: {
@@ -1257,7 +1287,7 @@ function buildTools(scope, authentication) {
1257
1287
  const query = queryFromArgs(args["query"]);
1258
1288
  const headers = headersFromArgs(args["headers"]);
1259
1289
  const upload = uploadBody(args);
1260
- const { result } = await callCloudflare(base, {
1290
+ const { result } = await callCloudflare(send, {
1261
1291
  method: String(args["method"]),
1262
1292
  path: cloudflareApiPath(args["path"]),
1263
1293
  ...(query !== undefined ? { query } : {}),
@@ -1288,7 +1318,7 @@ function buildTools(scope, authentication) {
1288
1318
  },
1289
1319
  outputSchema: listOutputSchema("accounts", ACCOUNT_SCHEMA),
1290
1320
  handler: async (args, ctx) => {
1291
- const { result, resultInfo } = await callCloudflare(base, {
1321
+ const { result, resultInfo } = await callCloudflare(send, {
1292
1322
  method: "GET",
1293
1323
  path: "/accounts",
1294
1324
  query: {
@@ -1333,7 +1363,7 @@ function buildTools(scope, authentication) {
1333
1363
  },
1334
1364
  outputSchema: listOutputSchema("zones", ZONE_SCHEMA),
1335
1365
  handler: async (args, ctx) => {
1336
- const { result, resultInfo } = await callCloudflare(base, {
1366
+ const { result, resultInfo } = await callCloudflare(send, {
1337
1367
  method: "GET",
1338
1368
  path: "/zones",
1339
1369
  query: {
@@ -1371,31 +1401,15 @@ function buildTools(scope, authentication) {
1371
1401
  },
1372
1402
  outputSchema: ZONE_SCHEMA,
1373
1403
  handler: async (args, ctx) => {
1374
- const { result } = await callCloudflare(base, { method: "GET", path: `/zones/${encodeURIComponent(zoneArg(args))}` }, ctx);
1404
+ const { result } = await callCloudflare(send, { method: "GET", path: `/zones/${encodeURIComponent(zoneArg(args))}` }, ctx);
1375
1405
  return args["raw"] === true ? result : projectZone(result);
1376
1406
  },
1377
1407
  },
1378
- {
1379
- name: "list_zone_settings",
1380
- description: "List the effective settings for a zone, including each setting's current value and whether the plan allows editing it.",
1381
- annotations: readOnly,
1382
- inputSchema: {
1383
- type: "object",
1384
- properties: {
1385
- zoneId: scopeProperty("zoneId", scope.zoneId),
1386
- },
1387
- required: scopeRequired("zoneId", scope.zoneId),
1388
- additionalProperties: false,
1389
- },
1390
- outputSchema: listOutputSchema("settings", OPEN_OBJECT_OUTPUT_SCHEMA),
1391
- handler: async (args, ctx) => {
1392
- const { result, resultInfo } = await callCloudflare(base, {
1393
- method: "GET",
1394
- path: `/zones/${encodePathSegment(zoneArg(args))}/settings`,
1395
- }, ctx);
1396
- return { settings: asArray(result), page: pageInfo(resultInfo) };
1397
- },
1398
- },
1408
+ // There is deliberately no bulk zone-settings read here. Cloudflare
1409
+ // publishes `GET /zones/{zone_id}/settings` as deprecated (#361), the
1410
+ // per-setting operations below are not, and the tool that wrapped the bulk
1411
+ // read projected nothing. See the Cloudflare guide's "What the named
1412
+ // surface deliberately leaves out".
1399
1413
  {
1400
1414
  name: "get_zone_setting",
1401
1415
  description: "Get one zone setting by its Cloudflare setting id, such as ssl, always_use_https, min_tls_version, brotli, or development_mode.",
@@ -1407,7 +1421,7 @@ function buildTools(scope, authentication) {
1407
1421
  settingId: {
1408
1422
  type: "string",
1409
1423
  minLength: 1,
1410
- description: "Cloudflare zone setting id from list_zone_settings.",
1424
+ description: "Cloudflare zone setting id, such as ssl, brotli, http3, or min_tls_version.",
1411
1425
  },
1412
1426
  },
1413
1427
  required: [...scopeRequired("zoneId", scope.zoneId), "settingId"],
@@ -1415,7 +1429,7 @@ function buildTools(scope, authentication) {
1415
1429
  },
1416
1430
  outputSchema: OPEN_OBJECT_OUTPUT_SCHEMA,
1417
1431
  handler: async (args, ctx) => {
1418
- const { result } = await callCloudflare(base, {
1432
+ const { result } = await callCloudflare(send, {
1419
1433
  method: "GET",
1420
1434
  path: `/zones/${encodePathSegment(zoneArg(args))}/settings/${encodePathSegment(requireString(args, "settingId"))}`,
1421
1435
  }, ctx);
@@ -1433,7 +1447,7 @@ function buildTools(scope, authentication) {
1433
1447
  settingId: {
1434
1448
  type: "string",
1435
1449
  minLength: 1,
1436
- description: "Cloudflare zone setting id from list_zone_settings.",
1450
+ description: "Cloudflare zone setting id, such as ssl, brotli, http3, or min_tls_version.",
1437
1451
  },
1438
1452
  value: {
1439
1453
  type: ["string", "number", "boolean", "array"],
@@ -1446,7 +1460,7 @@ function buildTools(scope, authentication) {
1446
1460
  },
1447
1461
  outputSchema: OPEN_OBJECT_OUTPUT_SCHEMA,
1448
1462
  handler: async (args, ctx) => {
1449
- const { result } = await callCloudflare(base, {
1463
+ const { result } = await callCloudflare(send, {
1450
1464
  method: "PATCH",
1451
1465
  path: `/zones/${encodePathSegment(zoneArg(args))}/settings/${encodePathSegment(requireString(args, "settingId"))}`,
1452
1466
  body: { value: args["value"] },
@@ -1468,10 +1482,7 @@ function buildTools(scope, authentication) {
1468
1482
  maximum: 50,
1469
1483
  description: "Rulesets per request, 1 to 50.",
1470
1484
  },
1471
- cursor: {
1472
- type: "string",
1473
- description: "Opaque cursor returned as nextCursor by the previous call.",
1474
- },
1485
+ cursor: CURSOR_INPUT_PROPERTY,
1475
1486
  },
1476
1487
  required: scopeRequired("zoneId", scope.zoneId),
1477
1488
  additionalProperties: false,
@@ -1480,12 +1491,12 @@ function buildTools(scope, authentication) {
1480
1491
  type: "object",
1481
1492
  properties: {
1482
1493
  rulesets: { type: "array", items: OPEN_OBJECT_OUTPUT_SCHEMA },
1483
- nextCursor: { type: "string" },
1494
+ nextCursor: NEXT_CURSOR_OUTPUT_PROPERTY,
1484
1495
  },
1485
1496
  required: ["rulesets"],
1486
1497
  },
1487
1498
  handler: async (args, ctx) => {
1488
- const { result, resultInfo } = await callCloudflare(base, {
1499
+ const { result, resultInfo } = await callCloudflare(send, {
1489
1500
  method: "GET",
1490
1501
  path: `/zones/${encodePathSegment(zoneArg(args))}/rulesets`,
1491
1502
  query: {
@@ -1521,7 +1532,7 @@ function buildTools(scope, authentication) {
1521
1532
  },
1522
1533
  outputSchema: OPEN_OBJECT_OUTPUT_SCHEMA,
1523
1534
  handler: async (args, ctx) => {
1524
- const { result } = await callCloudflare(base, {
1535
+ const { result } = await callCloudflare(send, {
1525
1536
  method: "GET",
1526
1537
  path: `/zones/${encodePathSegment(zoneArg(args))}/rulesets/${encodePathSegment(requireString(args, "rulesetId"))}`,
1527
1538
  }, ctx);
@@ -1573,7 +1584,7 @@ function buildTools(scope, authentication) {
1573
1584
  },
1574
1585
  outputSchema: listOutputSchema("records", DNS_RECORD_SCHEMA),
1575
1586
  handler: async (args, ctx) => {
1576
- const { result, resultInfo } = await callCloudflare(base, {
1587
+ const { result, resultInfo } = await callCloudflare(send, {
1577
1588
  method: "GET",
1578
1589
  path: `/zones/${encodeURIComponent(zoneArg(args))}/dns_records`,
1579
1590
  query: {
@@ -1613,7 +1624,7 @@ function buildTools(scope, authentication) {
1613
1624
  },
1614
1625
  outputSchema: DNS_RECORD_SCHEMA,
1615
1626
  handler: async (args, ctx) => {
1616
- const { result } = await callCloudflare(base, {
1627
+ const { result } = await callCloudflare(send, {
1617
1628
  method: "GET",
1618
1629
  path: `/zones/${encodeURIComponent(zoneArg(args))}/dns_records/${encodeURIComponent(String(args["recordId"]))}`,
1619
1630
  }, ctx);
@@ -1644,7 +1655,7 @@ function buildTools(scope, authentication) {
1644
1655
  required: ["id"],
1645
1656
  }),
1646
1657
  handler: async (args, ctx) => {
1647
- const { result, resultInfo } = await callCloudflare(base, {
1658
+ const { result, resultInfo } = await callCloudflare(send, {
1648
1659
  method: "GET",
1649
1660
  path: `/accounts/${encodeURIComponent(accountArg(args))}/workers/scripts`,
1650
1661
  }, ctx);
@@ -1675,7 +1686,7 @@ function buildTools(scope, authentication) {
1675
1686
  },
1676
1687
  outputSchema: OPEN_OBJECT_OUTPUT_SCHEMA,
1677
1688
  handler: async (args, ctx) => {
1678
- const { result } = await callCloudflare(base, {
1689
+ const { result } = await callCloudflare(send, {
1679
1690
  method: "GET",
1680
1691
  path: `/accounts/${encodePathSegment(accountArg(args))}/workers/scripts/${encodePathSegment(requireString(args, "scriptName"))}/settings`,
1681
1692
  }, ctx);
@@ -1701,7 +1712,7 @@ function buildTools(scope, authentication) {
1701
1712
  },
1702
1713
  outputSchema: listOutputSchema("deployments", OPEN_OBJECT_OUTPUT_SCHEMA),
1703
1714
  handler: async (args, ctx) => {
1704
- const { result } = await callCloudflare(base, {
1715
+ const { result } = await callCloudflare(send, {
1705
1716
  method: "GET",
1706
1717
  path: `/accounts/${encodePathSegment(accountArg(args))}/workers/scripts/${encodePathSegment(requireString(args, "scriptName"))}/deployments`,
1707
1718
  }, ctx);
@@ -1740,7 +1751,7 @@ function buildTools(scope, authentication) {
1740
1751
  },
1741
1752
  outputSchema: OPEN_OBJECT_OUTPUT_SCHEMA,
1742
1753
  handler: async (args, ctx) => {
1743
- const { result } = await callCloudflare(base, {
1754
+ const { result } = await callCloudflare(send, {
1744
1755
  method: "GET",
1745
1756
  path: `/accounts/${encodePathSegment(accountArg(args))}/workers/scripts/${encodePathSegment(requireString(args, "scriptName"))}/deployments/${encodePathSegment(requireString(args, "deploymentId"))}`,
1746
1757
  }, ctx);
@@ -1778,7 +1789,7 @@ function buildTools(scope, authentication) {
1778
1789
  },
1779
1790
  handler: async (args, ctx) => {
1780
1791
  const scriptName = requireString(args, "scriptName");
1781
- await callCloudflare(base, {
1792
+ await callCloudflare(send, {
1782
1793
  method: "DELETE",
1783
1794
  path: `/accounts/${encodePathSegment(accountArg(args))}/workers/scripts/${encodePathSegment(scriptName)}`,
1784
1795
  query: { force: optionalBoolean(args, "force") },
@@ -1810,7 +1821,7 @@ function buildTools(scope, authentication) {
1810
1821
  required: ["id", "title"],
1811
1822
  }),
1812
1823
  handler: async (args, ctx) => {
1813
- const { result, resultInfo } = await callCloudflare(base, {
1824
+ const { result, resultInfo } = await callCloudflare(send, {
1814
1825
  method: "GET",
1815
1826
  path: `/accounts/${encodeURIComponent(accountArg(args))}/storage/kv/namespaces`,
1816
1827
  query: {
@@ -1845,7 +1856,7 @@ function buildTools(scope, authentication) {
1845
1856
  },
1846
1857
  outputSchema: OPEN_OBJECT_OUTPUT_SCHEMA,
1847
1858
  handler: async (args, ctx) => {
1848
- const { result } = await callCloudflare(base, {
1859
+ const { result } = await callCloudflare(send, {
1849
1860
  method: "GET",
1850
1861
  path: `/accounts/${encodePathSegment(accountArg(args))}/storage/kv/namespaces/${encodePathSegment(requireString(args, "namespaceId"))}`,
1851
1862
  }, ctx);
@@ -1872,7 +1883,7 @@ function buildTools(scope, authentication) {
1872
1883
  },
1873
1884
  outputSchema: OPEN_OBJECT_OUTPUT_SCHEMA,
1874
1885
  handler: async (args, ctx) => {
1875
- const { result } = await callCloudflare(base, {
1886
+ const { result } = await callCloudflare(send, {
1876
1887
  method: "POST",
1877
1888
  path: `/accounts/${encodePathSegment(accountArg(args))}/storage/kv/namespaces`,
1878
1889
  body: { title: requireString(args, "title") },
@@ -1909,7 +1920,7 @@ function buildTools(scope, authentication) {
1909
1920
  },
1910
1921
  outputSchema: OPEN_OBJECT_OUTPUT_SCHEMA,
1911
1922
  handler: async (args, ctx) => {
1912
- const { result } = await callCloudflare(base, {
1923
+ const { result } = await callCloudflare(send, {
1913
1924
  method: "PUT",
1914
1925
  path: `/accounts/${encodePathSegment(accountArg(args))}/storage/kv/namespaces/${encodePathSegment(requireString(args, "namespaceId"))}`,
1915
1926
  body: { title: requireString(args, "title") },
@@ -1944,7 +1955,7 @@ function buildTools(scope, authentication) {
1944
1955
  },
1945
1956
  handler: async (args, ctx) => {
1946
1957
  const namespaceId = requireString(args, "namespaceId");
1947
- await callCloudflare(base, {
1958
+ await callCloudflare(send, {
1948
1959
  method: "DELETE",
1949
1960
  path: `/accounts/${encodePathSegment(accountArg(args))}/storage/kv/namespaces/${encodePathSegment(namespaceId)}`,
1950
1961
  }, ctx);
@@ -1974,10 +1985,7 @@ function buildTools(scope, authentication) {
1974
1985
  maximum: 1000,
1975
1986
  description: "Keys per request, 10 to 1000. Defaults to 1000.",
1976
1987
  },
1977
- cursor: {
1978
- type: "string",
1979
- description: "Opaque cursor returned as nextCursor by the previous call.",
1980
- },
1988
+ cursor: CURSOR_INPUT_PROPERTY,
1981
1989
  },
1982
1990
  required: [...scopeRequired("accountId", scope.accountId), "namespaceId"],
1983
1991
  additionalProperties: false,
@@ -1986,12 +1994,12 @@ function buildTools(scope, authentication) {
1986
1994
  type: "object",
1987
1995
  properties: {
1988
1996
  keys: { type: "array", items: OPEN_OBJECT_OUTPUT_SCHEMA },
1989
- nextCursor: { type: "string" },
1997
+ nextCursor: NEXT_CURSOR_OUTPUT_PROPERTY,
1990
1998
  },
1991
1999
  required: ["keys"],
1992
2000
  },
1993
2001
  handler: async (args, ctx) => {
1994
- const { result, resultInfo } = await callCloudflare(base, {
2002
+ const { result, resultInfo } = await callCloudflare(send, {
1995
2003
  method: "GET",
1996
2004
  path: `/accounts/${encodePathSegment(accountArg(args))}/storage/kv/namespaces/${encodePathSegment(requireString(args, "namespaceId"))}/keys`,
1997
2005
  query: {
@@ -2048,7 +2056,7 @@ function buildTools(scope, authentication) {
2048
2056
  },
2049
2057
  outputSchema: OPEN_OBJECT_OUTPUT_SCHEMA,
2050
2058
  handler: async (args, ctx) => {
2051
- const { result } = await callCloudflare(base, {
2059
+ const { result } = await callCloudflare(send, {
2052
2060
  method: "POST",
2053
2061
  path: `/accounts/${encodePathSegment(accountArg(args))}/storage/kv/namespaces/${encodePathSegment(requireString(args, "namespaceId"))}/bulk/get`,
2054
2062
  body: {
@@ -2083,12 +2091,34 @@ function buildTools(scope, authentication) {
2083
2091
  items: {
2084
2092
  type: "object",
2085
2093
  properties: {
2086
- key: { type: "string", minLength: 1, maxLength: 512 },
2087
- value: { type: "string", maxLength: 26_214_400 },
2088
- expiration: { type: "number" },
2089
- expiration_ttl: { type: "number", minimum: 60 },
2090
- metadata: { type: ["object", "array", "string", "number", "boolean", "null"] },
2091
- base64: { type: "boolean" },
2094
+ key: {
2095
+ type: "string",
2096
+ minLength: 1,
2097
+ maxLength: 512,
2098
+ description: "Key name, up to Cloudflare's 512 bytes.",
2099
+ },
2100
+ value: {
2101
+ type: "string",
2102
+ maxLength: 26_214_400,
2103
+ description: "Value, up to Cloudflare's 25 MiB.",
2104
+ },
2105
+ expiration: {
2106
+ type: "number",
2107
+ description: "Absolute expiry as a Unix timestamp in seconds.",
2108
+ },
2109
+ expiration_ttl: {
2110
+ type: "number",
2111
+ minimum: 60,
2112
+ description: "Relative expiry in seconds; Cloudflare's floor is 60.",
2113
+ },
2114
+ metadata: {
2115
+ type: ["object", "array", "string", "number", "boolean", "null"],
2116
+ description: "JSON metadata returned beside the key by list_kv_keys.",
2117
+ },
2118
+ base64: {
2119
+ type: "boolean",
2120
+ description: "Treat value as base64 and store the decoded bytes.",
2121
+ },
2092
2122
  },
2093
2123
  required: ["key", "value"],
2094
2124
  additionalProperties: false,
@@ -2104,7 +2134,7 @@ function buildTools(scope, authentication) {
2104
2134
  },
2105
2135
  outputSchema: OPEN_OBJECT_OUTPUT_SCHEMA,
2106
2136
  handler: async (args, ctx) => {
2107
- const { result } = await callCloudflare(base, {
2137
+ const { result } = await callCloudflare(send, {
2108
2138
  method: "PUT",
2109
2139
  path: `/accounts/${encodePathSegment(accountArg(args))}/storage/kv/namespaces/${encodePathSegment(requireString(args, "namespaceId"))}/bulk`,
2110
2140
  body: args["entries"],
@@ -2142,7 +2172,7 @@ function buildTools(scope, authentication) {
2142
2172
  },
2143
2173
  outputSchema: OPEN_OBJECT_OUTPUT_SCHEMA,
2144
2174
  handler: async (args, ctx) => {
2145
- const { result } = await callCloudflare(base, {
2175
+ const { result } = await callCloudflare(send, {
2146
2176
  method: "POST",
2147
2177
  path: `/accounts/${encodePathSegment(accountArg(args))}/storage/kv/namespaces/${encodePathSegment(requireString(args, "namespaceId"))}/bulk/delete`,
2148
2178
  body: args["keys"],
@@ -2168,10 +2198,7 @@ function buildTools(scope, authentication) {
2168
2198
  maximum: 1000,
2169
2199
  description: "Buckets per request, 1 to 1000. Defaults to 20.",
2170
2200
  },
2171
- cursor: {
2172
- type: "string",
2173
- description: "Opaque cursor from a previous call's nextCursor. R2 paginates by cursor, not page number.",
2174
- },
2201
+ cursor: CURSOR_INPUT_PROPERTY,
2175
2202
  jurisdiction: R2_JURISDICTION_PROPERTY,
2176
2203
  raw: RAW_INPUT_PROPERTY,
2177
2204
  },
@@ -2195,15 +2222,12 @@ function buildTools(scope, authentication) {
2195
2222
  required: ["name"],
2196
2223
  },
2197
2224
  },
2198
- nextCursor: {
2199
- type: "string",
2200
- description: "Pass back as `cursor` to continue. Absent when the listing is complete.",
2201
- },
2225
+ nextCursor: NEXT_CURSOR_OUTPUT_PROPERTY,
2202
2226
  },
2203
2227
  required: ["buckets"],
2204
2228
  },
2205
2229
  handler: async (args, ctx) => {
2206
- const { result, resultInfo } = await callCloudflare(base, {
2230
+ const { result, resultInfo } = await callCloudflare(send, {
2207
2231
  method: "GET",
2208
2232
  path: `/accounts/${encodeURIComponent(accountArg(args))}/r2/buckets`,
2209
2233
  query: {
@@ -2243,7 +2267,7 @@ function buildTools(scope, authentication) {
2243
2267
  },
2244
2268
  outputSchema: R2_BUCKET_SCHEMA,
2245
2269
  handler: async (args, ctx) => {
2246
- const { result } = await callCloudflare(base, {
2270
+ const { result } = await callCloudflare(send, {
2247
2271
  method: "GET",
2248
2272
  path: `/accounts/${encodePathSegment(accountArg(args))}/r2/buckets/${encodePathSegment(requireString(args, "bucketName"))}`,
2249
2273
  headers: r2Headers(args),
@@ -2277,7 +2301,7 @@ function buildTools(scope, authentication) {
2277
2301
  },
2278
2302
  outputSchema: R2_BUCKET_SCHEMA,
2279
2303
  handler: async (args, ctx) => {
2280
- const { result } = await callCloudflare(base, {
2304
+ const { result } = await callCloudflare(send, {
2281
2305
  method: "POST",
2282
2306
  path: `/accounts/${encodePathSegment(accountArg(args))}/r2/buckets`,
2283
2307
  headers: r2Headers(args),
@@ -2319,7 +2343,7 @@ function buildTools(scope, authentication) {
2319
2343
  },
2320
2344
  outputSchema: R2_BUCKET_SCHEMA,
2321
2345
  handler: async (args, ctx) => {
2322
- const { result } = await callCloudflare(base, {
2346
+ const { result } = await callCloudflare(send, {
2323
2347
  method: "PATCH",
2324
2348
  path: `/accounts/${encodePathSegment(accountArg(args))}/r2/buckets/${encodePathSegment(requireString(args, "bucketName"))}`,
2325
2349
  headers: {
@@ -2354,7 +2378,7 @@ function buildTools(scope, authentication) {
2354
2378
  },
2355
2379
  handler: async (args, ctx) => {
2356
2380
  const bucketName = requireString(args, "bucketName");
2357
- await callCloudflare(base, {
2381
+ await callCloudflare(send, {
2358
2382
  method: "DELETE",
2359
2383
  path: `/accounts/${encodePathSegment(accountArg(args))}/r2/buckets/${encodePathSegment(bucketName)}`,
2360
2384
  headers: r2Headers(args),
@@ -2392,10 +2416,7 @@ function buildTools(scope, authentication) {
2392
2416
  maximum: 1000,
2393
2417
  description: "Objects per request, 1 to 1000.",
2394
2418
  },
2395
- cursor: {
2396
- type: "string",
2397
- description: "Opaque cursor returned as nextCursor by the previous call.",
2398
- },
2419
+ cursor: CURSOR_INPUT_PROPERTY,
2399
2420
  },
2400
2421
  required: [...scopeRequired("accountId", scope.accountId), "bucketName"],
2401
2422
  additionalProperties: false,
@@ -2405,13 +2426,13 @@ function buildTools(scope, authentication) {
2405
2426
  properties: {
2406
2427
  objects: { type: "array", items: R2_OBJECT_SCHEMA },
2407
2428
  commonPrefixes: { type: "array", items: { type: "string" } },
2408
- nextCursor: { type: "string" },
2429
+ nextCursor: NEXT_CURSOR_OUTPUT_PROPERTY,
2409
2430
  truncated: { type: "boolean" },
2410
2431
  },
2411
2432
  required: ["objects", "truncated"],
2412
2433
  },
2413
2434
  handler: async (args, ctx) => {
2414
- const { result, resultInfo } = await callCloudflare(base, {
2435
+ const { result, resultInfo } = await callCloudflare(send, {
2415
2436
  method: "GET",
2416
2437
  path: `/accounts/${encodePathSegment(accountArg(args))}/r2/buckets/${encodePathSegment(requireString(args, "bucketName"))}/objects`,
2417
2438
  headers: r2Headers(args),
@@ -2469,7 +2490,7 @@ function buildTools(scope, authentication) {
2469
2490
  },
2470
2491
  handler: async (args, ctx) => {
2471
2492
  const objectKey = requireString(args, "objectKey");
2472
- await callCloudflare(base, {
2493
+ await callCloudflare(send, {
2473
2494
  method: "DELETE",
2474
2495
  path: `/accounts/${encodePathSegment(accountArg(args))}/r2/buckets/${encodePathSegment(requireString(args, "bucketName"))}/objects/${encodeObjectKey(objectKey)}`,
2475
2496
  headers: r2Headers(args),
@@ -2477,25 +2498,17 @@ function buildTools(scope, authentication) {
2477
2498
  return { deleted: true, objectKey };
2478
2499
  },
2479
2500
  },
2480
- {
2481
- name: "get_r2_metrics",
2482
- description: "Get account-level R2 object-count and storage-size metrics split by storage class and publication state.",
2483
- annotations: readOnly,
2484
- inputSchema: {
2485
- type: "object",
2486
- properties: { accountId: scopeProperty("accountId", scope.accountId) },
2487
- required: scopeRequired("accountId", scope.accountId),
2488
- additionalProperties: false,
2489
- },
2490
- outputSchema: OPEN_OBJECT_OUTPUT_SCHEMA,
2491
- handler: async (args, ctx) => {
2492
- const { result } = await callCloudflare(base, {
2493
- method: "GET",
2494
- path: `/accounts/${encodePathSegment(accountArg(args))}/r2/metrics`,
2495
- }, ctx);
2496
- return asRecord(result);
2497
- },
2498
- },
2501
+ // No `get_r2_metrics`, `set_r2_cors`, or `delete_r2_cors` here on purpose.
2502
+ // The #350 measurement found `get_r2_metrics` an unprojected,
2503
+ // output-schema-less wrapper around a path, and `set_r2_cors` carrying a
2504
+ // free-form `additionalProperties: true` rule body, so its schema did not
2505
+ // validate the part of the call that actually fails. `delete_r2_cors`
2506
+ // measured clean — a fixed `{deleted}` confirmation behind a closed output
2507
+ // schema and went anyway, as the other half of one policy pair: with the
2508
+ // write unnamed, a named delete would change a CORS policy through a
2509
+ // different route than setting it does. Reading a policy is still named;
2510
+ // changing one takes the approval-gated raw route, exactly as
2511
+ // structured-data DNS records already do.
2499
2512
  {
2500
2513
  name: "get_r2_cors",
2501
2514
  description: "Get the browser CORS rules configured on an R2 bucket.",
@@ -2512,7 +2525,7 @@ function buildTools(scope, authentication) {
2512
2525
  },
2513
2526
  outputSchema: OPEN_OBJECT_OUTPUT_SCHEMA,
2514
2527
  handler: async (args, ctx) => {
2515
- const { result } = await callCloudflare(base, {
2528
+ const { result } = await callCloudflare(send, {
2516
2529
  method: "GET",
2517
2530
  path: `/accounts/${encodePathSegment(accountArg(args))}/r2/buckets/${encodePathSegment(requireString(args, "bucketName"))}/cors`,
2518
2531
  headers: r2Headers(args),
@@ -2520,65 +2533,6 @@ function buildTools(scope, authentication) {
2520
2533
  return asRecord(result);
2521
2534
  },
2522
2535
  },
2523
- {
2524
- name: "set_r2_cors",
2525
- description: "Replace an R2 bucket's CORS policy. Supply the complete desired rule list; omitted existing rules are removed.",
2526
- annotations: { readOnlyHint: false, destructiveHint: true },
2527
- inputSchema: {
2528
- type: "object",
2529
- properties: {
2530
- accountId: scopeProperty("accountId", scope.accountId),
2531
- bucketName: R2_BUCKET_NAME_PROPERTY,
2532
- jurisdiction: R2_JURISDICTION_PROPERTY,
2533
- rules: {
2534
- type: "array",
2535
- maxItems: 100,
2536
- description: "Complete CORS rule list using Cloudflare fields: allowed.methods, allowed.origins, optional allowed.headers, id, exposeHeaders, and maxAgeSeconds.",
2537
- items: { type: "object", additionalProperties: true },
2538
- },
2539
- },
2540
- required: [...scopeRequired("accountId", scope.accountId), "bucketName", "rules"],
2541
- additionalProperties: false,
2542
- },
2543
- outputSchema: OPEN_OBJECT_OUTPUT_SCHEMA,
2544
- handler: async (args, ctx) => {
2545
- const { result } = await callCloudflare(base, {
2546
- method: "PUT",
2547
- path: `/accounts/${encodePathSegment(accountArg(args))}/r2/buckets/${encodePathSegment(requireString(args, "bucketName"))}/cors`,
2548
- headers: r2Headers(args),
2549
- body: { rules: args["rules"] },
2550
- }, ctx);
2551
- return asRecord(result);
2552
- },
2553
- },
2554
- {
2555
- name: "delete_r2_cors",
2556
- description: "Remove the complete CORS policy from an R2 bucket.",
2557
- annotations: { readOnlyHint: false, destructiveHint: true },
2558
- inputSchema: {
2559
- type: "object",
2560
- properties: {
2561
- accountId: scopeProperty("accountId", scope.accountId),
2562
- bucketName: R2_BUCKET_NAME_PROPERTY,
2563
- jurisdiction: R2_JURISDICTION_PROPERTY,
2564
- },
2565
- required: [...scopeRequired("accountId", scope.accountId), "bucketName"],
2566
- additionalProperties: false,
2567
- },
2568
- outputSchema: {
2569
- type: "object",
2570
- properties: { deleted: { type: "boolean" } },
2571
- required: ["deleted"],
2572
- },
2573
- handler: async (args, ctx) => {
2574
- await callCloudflare(base, {
2575
- method: "DELETE",
2576
- path: `/accounts/${encodePathSegment(accountArg(args))}/r2/buckets/${encodePathSegment(requireString(args, "bucketName"))}/cors`,
2577
- headers: r2Headers(args),
2578
- }, ctx);
2579
- return { deleted: true };
2580
- },
2581
- },
2582
2536
  {
2583
2537
  name: "list_pages_projects",
2584
2538
  description: "List Cloudflare Pages projects in an account, with their production branch and latest deployment.",
@@ -2614,7 +2568,7 @@ function buildTools(scope, authentication) {
2614
2568
  required: ["name"],
2615
2569
  }),
2616
2570
  handler: async (args, ctx) => {
2617
- const { result, resultInfo } = await callCloudflare(base, {
2571
+ const { result, resultInfo } = await callCloudflare(send, {
2618
2572
  method: "GET",
2619
2573
  path: `/accounts/${encodeURIComponent(accountArg(args))}/pages/projects`,
2620
2574
  query: {
@@ -2650,7 +2604,7 @@ function buildTools(scope, authentication) {
2650
2604
  },
2651
2605
  outputSchema: OPEN_OBJECT_OUTPUT_SCHEMA,
2652
2606
  handler: async (args, ctx) => {
2653
- const { result } = await callCloudflare(base, {
2607
+ const { result } = await callCloudflare(send, {
2654
2608
  method: "GET",
2655
2609
  path: `/accounts/${encodePathSegment(accountArg(args))}/pages/projects/${encodePathSegment(requireString(args, "projectName"))}`,
2656
2610
  }, ctx);
@@ -2682,7 +2636,7 @@ function buildTools(scope, authentication) {
2682
2636
  },
2683
2637
  outputSchema: listOutputSchema("deployments", OPEN_OBJECT_OUTPUT_SCHEMA),
2684
2638
  handler: async (args, ctx) => {
2685
- const { result, resultInfo } = await callCloudflare(base, {
2639
+ const { result, resultInfo } = await callCloudflare(send, {
2686
2640
  method: "GET",
2687
2641
  path: `/accounts/${encodePathSegment(accountArg(args))}/pages/projects/${encodePathSegment(requireString(args, "projectName"))}/deployments`,
2688
2642
  query: {
@@ -2726,7 +2680,7 @@ function buildTools(scope, authentication) {
2726
2680
  },
2727
2681
  outputSchema: OPEN_OBJECT_OUTPUT_SCHEMA,
2728
2682
  handler: async (args, ctx) => {
2729
- const { result } = await callCloudflare(base, {
2683
+ const { result } = await callCloudflare(send, {
2730
2684
  method: "GET",
2731
2685
  path: `/accounts/${encodePathSegment(accountArg(args))}/pages/projects/${encodePathSegment(requireString(args, "projectName"))}/deployments/${encodePathSegment(requireString(args, "deploymentId"))}`,
2732
2686
  }, ctx);
@@ -2749,7 +2703,7 @@ function buildTools(scope, authentication) {
2749
2703
  },
2750
2704
  outputSchema: OPEN_OBJECT_OUTPUT_SCHEMA,
2751
2705
  handler: async (args, ctx) => {
2752
- const { result } = await callCloudflare(base, {
2706
+ const { result } = await callCloudflare(send, {
2753
2707
  method: "POST",
2754
2708
  path: `/accounts/${encodePathSegment(accountArg(args))}/pages/projects/${encodePathSegment(requireString(args, "projectName"))}/deployments/${encodePathSegment(requireString(args, "deploymentId"))}/retry`,
2755
2709
  }, ctx);
@@ -2772,7 +2726,7 @@ function buildTools(scope, authentication) {
2772
2726
  },
2773
2727
  outputSchema: OPEN_OBJECT_OUTPUT_SCHEMA,
2774
2728
  handler: async (args, ctx) => {
2775
- const { result } = await callCloudflare(base, {
2729
+ const { result } = await callCloudflare(send, {
2776
2730
  method: "POST",
2777
2731
  path: `/accounts/${encodePathSegment(accountArg(args))}/pages/projects/${encodePathSegment(requireString(args, "projectName"))}/deployments/${encodePathSegment(requireString(args, "deploymentId"))}/rollback`,
2778
2732
  }, ctx);
@@ -2796,7 +2750,7 @@ function buildTools(scope, authentication) {
2796
2750
  outputSchema: { type: "object", properties: { deleted: { type: "boolean" }, deploymentId: { type: "string" } }, required: ["deleted", "deploymentId"] },
2797
2751
  handler: async (args, ctx) => {
2798
2752
  const deploymentId = requireString(args, "deploymentId");
2799
- await callCloudflare(base, {
2753
+ await callCloudflare(send, {
2800
2754
  method: "DELETE",
2801
2755
  path: `/accounts/${encodePathSegment(accountArg(args))}/pages/projects/${encodePathSegment(requireString(args, "projectName"))}/deployments/${encodePathSegment(deploymentId)}`,
2802
2756
  }, ctx);
@@ -2818,7 +2772,7 @@ function buildTools(scope, authentication) {
2818
2772
  },
2819
2773
  outputSchema: listOutputSchema("domains", OPEN_OBJECT_OUTPUT_SCHEMA),
2820
2774
  handler: async (args, ctx) => {
2821
- const { result } = await callCloudflare(base, {
2775
+ const { result } = await callCloudflare(send, {
2822
2776
  method: "GET",
2823
2777
  path: `/accounts/${encodePathSegment(accountArg(args))}/pages/projects/${encodePathSegment(requireString(args, "projectName"))}/domains`,
2824
2778
  }, ctx);
@@ -2841,7 +2795,7 @@ function buildTools(scope, authentication) {
2841
2795
  },
2842
2796
  outputSchema: OPEN_OBJECT_OUTPUT_SCHEMA,
2843
2797
  handler: async (args, ctx) => {
2844
- const { result } = await callCloudflare(base, {
2798
+ const { result } = await callCloudflare(send, {
2845
2799
  method: "POST",
2846
2800
  path: `/accounts/${encodePathSegment(accountArg(args))}/pages/projects/${encodePathSegment(requireString(args, "projectName"))}/domains`,
2847
2801
  body: { name: requireString(args, "domain") },
@@ -2866,7 +2820,7 @@ function buildTools(scope, authentication) {
2866
2820
  outputSchema: { type: "object", properties: { deleted: { type: "boolean" }, domain: { type: "string" } }, required: ["deleted", "domain"] },
2867
2821
  handler: async (args, ctx) => {
2868
2822
  const domain = requireString(args, "domain");
2869
- await callCloudflare(base, {
2823
+ await callCloudflare(send, {
2870
2824
  method: "DELETE",
2871
2825
  path: `/accounts/${encodePathSegment(accountArg(args))}/pages/projects/${encodePathSegment(requireString(args, "projectName"))}/domains/${encodePathSegment(domain)}`,
2872
2826
  }, ctx);
@@ -2888,7 +2842,7 @@ function buildTools(scope, authentication) {
2888
2842
  },
2889
2843
  outputSchema: { type: "object", properties: { purged: { type: "boolean" } }, required: ["purged"] },
2890
2844
  handler: async (args, ctx) => {
2891
- await callCloudflare(base, {
2845
+ await callCloudflare(send, {
2892
2846
  method: "POST",
2893
2847
  path: `/accounts/${encodePathSegment(accountArg(args))}/pages/projects/${encodePathSegment(requireString(args, "projectName"))}/purge_build_cache`,
2894
2848
  }, ctx);
@@ -2911,7 +2865,7 @@ function buildTools(scope, authentication) {
2911
2865
  outputSchema: { type: "object", properties: { deleted: { type: "boolean" }, projectName: { type: "string" } }, required: ["deleted", "projectName"] },
2912
2866
  handler: async (args, ctx) => {
2913
2867
  const projectName = requireString(args, "projectName");
2914
- await callCloudflare(base, {
2868
+ await callCloudflare(send, {
2915
2869
  method: "DELETE",
2916
2870
  path: `/accounts/${encodePathSegment(accountArg(args))}/pages/projects/${encodePathSegment(projectName)}`,
2917
2871
  }, ctx);
@@ -2923,7 +2877,7 @@ function buildTools(scope, authentication) {
2923
2877
  // `destructiveHint` stays unset. `readOnlyHint: false` already routes it
2924
2878
  // through call_destructive_tool.
2925
2879
  name: "create_dns_record",
2926
- description: "Create a content-based DNS record (A, AAAA, CNAME, MX, NS, OPENPGPKEY, PTR, TXT) in a zone. Check for an existing record with list_dns_records first: Cloudflare rejects a duplicate rather than replacing it. Record types that carry structured data, such as SRV and CAA, are readable here but not creatable.",
2880
+ description: "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.",
2927
2881
  annotations: { readOnlyHint: false },
2928
2882
  inputSchema: {
2929
2883
  type: "object",
@@ -2978,7 +2932,7 @@ function buildTools(scope, authentication) {
2978
2932
  },
2979
2933
  outputSchema: DNS_RECORD_SCHEMA,
2980
2934
  handler: async (args, ctx) => {
2981
- const { result } = await callCloudflare(base, {
2935
+ const { result } = await callCloudflare(send, {
2982
2936
  method: "POST",
2983
2937
  path: `/zones/${encodeURIComponent(zoneArg(args))}/dns_records`,
2984
2938
  body: {
@@ -3079,7 +3033,7 @@ function buildTools(scope, authentication) {
3079
3033
  },
3080
3034
  });
3081
3035
  }
3082
- const { result } = await callCloudflare(base, {
3036
+ const { result } = await callCloudflare(send, {
3083
3037
  method: "PATCH",
3084
3038
  path: `/zones/${encodeURIComponent(zoneArg(args))}/dns_records/${encodeURIComponent(String(args["recordId"]))}`,
3085
3039
  body,
@@ -3113,7 +3067,7 @@ function buildTools(scope, authentication) {
3113
3067
  },
3114
3068
  handler: async (args, ctx) => {
3115
3069
  const recordId = String(args["recordId"]);
3116
- await callCloudflare(base, {
3070
+ await callCloudflare(send, {
3117
3071
  method: "DELETE",
3118
3072
  path: `/zones/${encodeURIComponent(zoneArg(args))}/dns_records/${encodeURIComponent(recordId)}`,
3119
3073
  }, ctx);
@@ -3213,7 +3167,7 @@ function buildTools(scope, authentication) {
3213
3167
  });
3214
3168
  }
3215
3169
  const variant = everything ? "everything" : targeted[0];
3216
- await callCloudflare(base, {
3170
+ await callCloudflare(send, {
3217
3171
  method: "POST",
3218
3172
  path: `/zones/${encodeURIComponent(zoneId)}/purge_cache`,
3219
3173
  body: everything
@@ -3246,10 +3200,11 @@ Account purpose: ${purpose}
3246
3200
 
3247
3201
  - ${zoneLine}
3248
3202
  - ${accountLine}
3249
- - 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\`, ETags, and object metadata; authentication, host, content type, and request framing remain connector-owned.
3203
+ - 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.
3250
3204
  - 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.
3251
3205
  - 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.
3252
- - 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\` instead return \`nextCursor\`; \`list_worker_scripts\` is unpaginated.
3206
+ - 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.
3207
+ - 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.
3253
3208
  - 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.
3254
3209
  - ${authenticationLine}
3255
3210
  - 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.
@@ -3273,7 +3228,7 @@ export function cloudflare(id, options) {
3273
3228
  throw new Error('cloudflare() authentication must be "apiToken" or "globalApiKey".');
3274
3229
  }
3275
3230
  const scope = {
3276
- base: options.baseUrl?.trim() || CLOUDFLARE_API_BASE,
3231
+ send: cloudflareTransport(options.baseUrl?.trim() || CLOUDFLARE_API_BASE),
3277
3232
  accountId: options.accountId?.trim() || undefined,
3278
3233
  zoneId: options.zoneId?.trim() || undefined,
3279
3234
  };
@@ -3282,10 +3237,18 @@ export function cloudflare(id, options) {
3282
3237
  description: `Cloudflare control-plane access for zones, DNS, Workers, KV, R2, Pages, media, email, and other v4 APIs — ${purpose}`,
3283
3238
  credential: credentialConfig(authentication, options.credential),
3284
3239
  callAdmission: admissionPolicy(maxConcurrency),
3285
- usageGuide: usageGuide(purpose, scope, options.instructions, authentication),
3286
- // The schemas are hand-written and closed; a schema that cannot be
3287
- // enforced is a bug in this file, not input to pass through.
3288
- strictValidation: true,
3240
+ usageGuide: {
3241
+ content: usageGuide(purpose, scope, options.instructions, authentication),
3242
+ // Explicit rather than derived: the first content line is the zone
3243
+ // scoping rule, which varies per deployment and reads as an instruction
3244
+ // rather than as the routing fact a browsing agent needs.
3245
+ summary: "Zone and account scoping, named-vs-raw routing, two pagination shapes, and lean-vs-raw results.",
3246
+ // Deliberately not `required`. Every named tool's schema is complete
3247
+ // enough to call it correctly on its own, and the scoping convention the
3248
+ // guide carries is repeated on each `zoneId` and `accountId` property —
3249
+ // so forcing the guide into context before every operation would spend
3250
+ // tokens on a sequence the schemas already express.
3251
+ },
3289
3252
  ...(options.maxResultBytes !== undefined
3290
3253
  ? { maxResultBytes: options.maxResultBytes }
3291
3254
  : {}),
@@ -3294,7 +3257,7 @@ export function cloudflare(id, options) {
3294
3257
  ? {
3295
3258
  async testCredential(value, ctx) {
3296
3259
  try {
3297
- const { result } = await callCloudflare(scope.base, { method: "GET", path: "/user/tokens/verify" }, withAuthentication({
3260
+ const { result } = await callCloudflare(scope.send, { method: "GET", path: "/user/tokens/verify" }, withAuthentication({
3298
3261
  ...ctx,
3299
3262
  credential: {
3300
3263
  get: async () => value,
@@ -3317,7 +3280,7 @@ export function cloudflare(id, options) {
3317
3280
  : {
3318
3281
  async testCredentials(values, ctx) {
3319
3282
  try {
3320
- const { result } = await callCloudflare(scope.base, { method: "GET", path: "/user" }, withAuthentication({
3283
+ const { result } = await callCloudflare(scope.send, { method: "GET", path: "/user" }, withAuthentication({
3321
3284
  ...ctx,
3322
3285
  credential: {
3323
3286
  get: async (field) => field ? values[field] ?? null : null,
@@ -3340,4 +3303,3 @@ export function cloudflare(id, options) {
3340
3303
  }),
3341
3304
  });
3342
3305
  }
3343
- //# sourceMappingURL=cloudflare.js.map