@zackbart/connecta 0.15.1 → 0.16.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/AGENTS.md +28 -15
  2. package/CHANGELOG.md +466 -0
  3. package/README.md +19 -4
  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 +0 -1
  27. package/dist/catalog.d.ts +0 -1
  28. package/dist/catalog.js +0 -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 +0 -1
  42. package/dist/errors.js +0 -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 +250 -276
  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 +58 -8
  129. package/documentation/code-mode.md +12 -12
  130. package/documentation/connector-guides.md +172 -5
  131. package/documentation/connectors.md +89 -0
  132. package/documentation/linear.md +20 -2
  133. package/documentation/mcp-ui-design.md +2 -2
  134. package/documentation/mixpanel.md +42 -9
  135. package/documentation/notion.md +16 -0
  136. package/documentation/operations.md +283 -5
  137. package/documentation/operator-ui.md +120 -5
  138. package/documentation/provider-audit.md +160 -0
  139. package/documentation/provider-conventions.md +631 -0
  140. package/documentation/request-admission.md +167 -5
  141. package/documentation/rich-output-design.md +1 -2
  142. package/documentation/stripe.md +15 -0
  143. package/ethos.md +13 -8
  144. package/examples/worker/README.md +96 -12
  145. package/examples/worker/src/index.ts +46 -1
  146. package/examples/worker/wrangler.jsonc +14 -1
  147. package/package.json +4 -5
  148. package/templates/node/.dockerignore +10 -0
  149. package/templates/node/.env.example +40 -3
  150. package/templates/node/AGENTS.md +11 -1
  151. package/templates/node/Dockerfile +49 -0
  152. package/templates/node/README.md +117 -2
  153. package/templates/node/docker-compose.yml +39 -0
  154. package/templates/node/package.json +1 -1
  155. package/templates/node/src/file-activity.ts +182 -0
  156. package/templates/node/src/index.ts +80 -4
  157. package/assets/connecta-clay-hero.png +0 -0
  158. package/dist/access-tokens.d.ts.map +0 -1
  159. package/dist/access-tokens.js.map +0 -1
  160. package/dist/activity.d.ts.map +0 -1
  161. package/dist/activity.js.map +0 -1
  162. package/dist/apps-shell.d.ts.map +0 -1
  163. package/dist/apps-shell.js.map +0 -1
  164. package/dist/auth/bearer.d.ts.map +0 -1
  165. package/dist/auth/bearer.js.map +0 -1
  166. package/dist/auth/clerk.d.ts.map +0 -1
  167. package/dist/auth/clerk.js.map +0 -1
  168. package/dist/auth/downstream-oauth.d.ts.map +0 -1
  169. package/dist/auth/downstream-oauth.js.map +0 -1
  170. package/dist/call-admission.d.ts.map +0 -1
  171. package/dist/call-admission.js.map +0 -1
  172. package/dist/catalog-fingerprint.d.ts.map +0 -1
  173. package/dist/catalog-fingerprint.js.map +0 -1
  174. package/dist/catalog-limits.d.ts.map +0 -1
  175. package/dist/catalog-limits.js.map +0 -1
  176. package/dist/catalog-service.d.ts.map +0 -1
  177. package/dist/catalog-service.js.map +0 -1
  178. package/dist/catalog.d.ts.map +0 -1
  179. package/dist/catalog.js.map +0 -1
  180. package/dist/concurrency.d.ts.map +0 -1
  181. package/dist/concurrency.js.map +0 -1
  182. package/dist/connector-scope.d.ts.map +0 -1
  183. package/dist/connector-scope.js.map +0 -1
  184. package/dist/connectors/api.d.ts.map +0 -1
  185. package/dist/connectors/api.js.map +0 -1
  186. package/dist/connectors/remote-mcp.d.ts.map +0 -1
  187. package/dist/connectors/remote-mcp.js.map +0 -1
  188. package/dist/credentials.d.ts.map +0 -1
  189. package/dist/credentials.js.map +0 -1
  190. package/dist/errors.d.ts.map +0 -1
  191. package/dist/errors.js.map +0 -1
  192. package/dist/execute.d.ts.map +0 -1
  193. package/dist/execute.js.map +0 -1
  194. package/dist/executor-admission.d.ts.map +0 -1
  195. package/dist/executor-admission.js.map +0 -1
  196. package/dist/executor-result.d.ts.map +0 -1
  197. package/dist/executor-result.js.map +0 -1
  198. package/dist/executors/quickjs-child.d.ts.map +0 -1
  199. package/dist/executors/quickjs-child.js.map +0 -1
  200. package/dist/executors/quickjs-protocol.d.ts.map +0 -1
  201. package/dist/executors/quickjs-protocol.js.map +0 -1
  202. package/dist/executors/quickjs-runtime.d.ts.map +0 -1
  203. package/dist/executors/quickjs-runtime.js.map +0 -1
  204. package/dist/executors/quickjs.d.ts.map +0 -1
  205. package/dist/executors/quickjs.js.map +0 -1
  206. package/dist/favicon.d.ts.map +0 -1
  207. package/dist/favicon.js.map +0 -1
  208. package/dist/index.d.ts.map +0 -1
  209. package/dist/index.js.map +0 -1
  210. package/dist/invocation.d.ts.map +0 -1
  211. package/dist/invocation.js.map +0 -1
  212. package/dist/json-schema.d.ts.map +0 -1
  213. package/dist/json-schema.js.map +0 -1
  214. package/dist/mcp-result.d.ts.map +0 -1
  215. package/dist/mcp-result.js.map +0 -1
  216. package/dist/meta-tools.d.ts.map +0 -1
  217. package/dist/meta-tools.js.map +0 -1
  218. package/dist/node.d.ts.map +0 -1
  219. package/dist/node.js.map +0 -1
  220. package/dist/operator-ui/generated.d.ts.map +0 -1
  221. package/dist/operator-ui/generated.js.map +0 -1
  222. package/dist/operator-ui/model.d.ts.map +0 -1
  223. package/dist/operator-ui/model.js.map +0 -1
  224. package/dist/providers/cloudflare.d.ts.map +0 -1
  225. package/dist/providers/cloudflare.js.map +0 -1
  226. package/dist/providers/linear.d.ts.map +0 -1
  227. package/dist/providers/linear.js.map +0 -1
  228. package/dist/providers/mixpanel.d.ts.map +0 -1
  229. package/dist/providers/mixpanel.js.map +0 -1
  230. package/dist/providers/notion.d.ts.map +0 -1
  231. package/dist/providers/notion.js.map +0 -1
  232. package/dist/providers/stripe.d.ts.map +0 -1
  233. package/dist/providers/stripe.js.map +0 -1
  234. package/dist/registry.d.ts.map +0 -1
  235. package/dist/registry.js.map +0 -1
  236. package/dist/retired-toolkits.d.ts.map +0 -1
  237. package/dist/retired-toolkits.js.map +0 -1
  238. package/dist/routes/access-tokens.d.ts.map +0 -1
  239. package/dist/routes/access-tokens.js.map +0 -1
  240. package/dist/routes/activity.d.ts.map +0 -1
  241. package/dist/routes/activity.js.map +0 -1
  242. package/dist/routes/credentials.d.ts.map +0 -1
  243. package/dist/routes/credentials.js.map +0 -1
  244. package/dist/routes/mcp.d.ts.map +0 -1
  245. package/dist/routes/mcp.js.map +0 -1
  246. package/dist/routes/oauth.d.ts.map +0 -1
  247. package/dist/routes/oauth.js.map +0 -1
  248. package/dist/routes/shared.d.ts.map +0 -1
  249. package/dist/routes/shared.js.map +0 -1
  250. package/dist/routes/ui.d.ts.map +0 -1
  251. package/dist/routes/ui.js.map +0 -1
  252. package/dist/server.d.ts.map +0 -1
  253. package/dist/server.js.map +0 -1
  254. package/dist/skills.d.ts.map +0 -1
  255. package/dist/skills.js.map +0 -1
  256. package/dist/storage/file.d.ts.map +0 -1
  257. package/dist/storage/file.js.map +0 -1
  258. package/dist/storage/memory.d.ts.map +0 -1
  259. package/dist/storage/memory.js.map +0 -1
  260. package/dist/timeout.d.ts.map +0 -1
  261. package/dist/timeout.js.map +0 -1
  262. package/dist/tool-safety.d.ts.map +0 -1
  263. package/dist/tool-safety.js.map +0 -1
  264. package/dist/types.d.ts.map +0 -1
  265. package/dist/types.js.map +0 -1
  266. package/dist/ui.d.ts.map +0 -1
  267. package/dist/ui.js.map +0 -1
  268. package/dist/validate.d.ts.map +0 -1
  269. package/dist/validate.js.map +0 -1
  270. package/dist/version.d.ts.map +0 -1
  271. package/dist/version.js.map +0 -1
  272. package/examples/node/README.md +0 -52
  273. package/examples/node/src/index.ts +0 -71
  274. package/src/access-tokens.ts +0 -289
  275. package/src/activity.ts +0 -234
  276. package/src/apps-shell.ts +0 -370
  277. package/src/auth/bearer.ts +0 -61
  278. package/src/auth/clerk.ts +0 -553
  279. package/src/auth/downstream-oauth.ts +0 -575
  280. package/src/call-admission.ts +0 -521
  281. package/src/catalog-fingerprint.ts +0 -52
  282. package/src/catalog-limits.ts +0 -11
  283. package/src/catalog-service.ts +0 -1153
  284. package/src/catalog.ts +0 -790
  285. package/src/concurrency.ts +0 -39
  286. package/src/connector-scope.ts +0 -66
  287. package/src/connectors/api.ts +0 -159
  288. package/src/connectors/remote-mcp.ts +0 -1090
  289. package/src/credentials.ts +0 -428
  290. package/src/errors.ts +0 -364
  291. package/src/execute.ts +0 -1406
  292. package/src/executor-admission.ts +0 -328
  293. package/src/executor-result.ts +0 -107
  294. package/src/executors/quickjs-child.ts +0 -172
  295. package/src/executors/quickjs-protocol.ts +0 -99
  296. package/src/executors/quickjs-runtime.ts +0 -470
  297. package/src/executors/quickjs.ts +0 -776
  298. package/src/favicon.ts +0 -53
  299. package/src/index.ts +0 -666
  300. package/src/invocation.ts +0 -655
  301. package/src/json-schema.ts +0 -11
  302. package/src/mcp-result.ts +0 -43
  303. package/src/meta-tools.ts +0 -1607
  304. package/src/node.ts +0 -206
  305. package/src/operator-ui/browser.css +0 -423
  306. package/src/operator-ui/browser.ts +0 -1281
  307. package/src/operator-ui/generated.ts +0 -4
  308. package/src/operator-ui/model.ts +0 -111
  309. package/src/providers/cloudflare.ts +0 -3999
  310. package/src/providers/linear.ts +0 -302
  311. package/src/providers/mixpanel.ts +0 -229
  312. package/src/providers/notion.ts +0 -1879
  313. package/src/providers/stripe.ts +0 -307
  314. package/src/registry.ts +0 -1085
  315. package/src/retired-toolkits.ts +0 -14
  316. package/src/routes/access-tokens.ts +0 -115
  317. package/src/routes/activity.ts +0 -215
  318. package/src/routes/credentials.ts +0 -254
  319. package/src/routes/mcp.ts +0 -429
  320. package/src/routes/oauth.ts +0 -322
  321. package/src/routes/shared.ts +0 -247
  322. package/src/routes/ui.ts +0 -152
  323. package/src/server.ts +0 -195
  324. package/src/skills.ts +0 -271
  325. package/src/storage/file.ts +0 -123
  326. package/src/storage/memory.ts +0 -44
  327. package/src/timeout.ts +0 -84
  328. package/src/tool-safety.ts +0 -15
  329. package/src/types.ts +0 -523
  330. package/src/ui.ts +0 -744
  331. package/src/validate.ts +0 -387
  332. 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";
@@ -242,6 +243,16 @@ function failureFor(status, headers, errors) {
242
243
  }
243
244
  return new ConnectorCallError("connector_call_failed", `Cloudflare request failed (HTTP ${status}). ${detail}`);
244
245
  }
246
+ // --- The request path --------------------------------------------------------
247
+ /**
248
+ * The largest response this connection will read.
249
+ *
250
+ * Generous rather than tight, because `cloudflare_api_get` legitimately
251
+ * downloads Worker scripts and R2 objects. It is a ceiling on absurdity, not a
252
+ * quota: anything approaching it is already far past whatever `maxResultBytes`
253
+ * the deployment set, so the caller was never going to see it whole.
254
+ */
255
+ const CLOUDFLARE_MAX_RESPONSE_BYTES = 8 * 1024 * 1024;
245
256
  const AUTHENTICATION_CONTEXT = Symbol("cloudflareAuthentication");
246
257
  function withAuthentication(ctx, authentication) {
247
258
  return { ...ctx, [AUTHENTICATION_CONTEXT]: authentication };
@@ -262,72 +273,63 @@ async function readAuthenticationHeaders(ctx) {
262
273
  }
263
274
  return { Authorization: `Bearer ${token}` };
264
275
  }
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;
276
+ /**
277
+ * The one transport every Cloudflare tool goes through.
278
+ *
279
+ * URL confinement, `ctx.signal`, redirect refusal, bounded reads, and the
280
+ * "could not reach the provider" normalization all live in the shared helper.
281
+ * What stays here is what only Cloudflare knows: which headers prove identity,
282
+ * and what a status code means once it arrives.
283
+ */
284
+ function cloudflareTransport(baseUrl) {
285
+ return guardedFetch({
286
+ provider: "Cloudflare",
287
+ baseUrl,
288
+ headers: { Accept: "application/json" },
289
+ maxResponseBytes: CLOUDFLARE_MAX_RESPONSE_BYTES,
290
+ authenticate: (ctx) => readAuthenticationHeaders(ctx),
291
+ });
303
292
  }
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
- };
293
+ async function callCloudflare(send, spec, ctx) {
294
+ return await send(spec, ctx, async (response) => {
295
+ let envelope;
296
+ let parseFailure;
297
+ try {
298
+ envelope = (await response.json());
299
+ }
300
+ catch (cause) {
301
+ // A transport failure is not a parse failure. The connector's byte
302
+ // ceiling fires from inside this read and is deliberately non-retryable;
303
+ // routing it through the branch below would relabel it as a retryable
304
+ // `unavailable` and tell an agent to retry a response that will exceed
305
+ // the ceiling every time.
306
+ if (cause instanceof ConnectorCallError)
307
+ throw cause;
308
+ parseFailure = cause;
309
+ }
310
+ if (envelope === undefined) {
311
+ // A gateway error page or an empty body, not an envelope: the status is
312
+ // the only real signal left.
313
+ throw response.ok
314
+ ? new ConnectorCallError("unavailable", "Cloudflare returned a non-JSON body for a successful status.", parseFailure !== undefined ? { cause: parseFailure } : {})
315
+ : failureFor(response.status, response.headers, []);
316
+ }
317
+ const errors = Array.isArray(envelope.errors) ? envelope.errors : [];
318
+ if (!response.ok || envelope.success === false) {
319
+ throw failureFor(response.status, response.headers, errors);
320
+ }
321
+ const isV4Envelope = "success" in envelope ||
322
+ "result" in envelope ||
323
+ "result_info" in envelope ||
324
+ "messages" in envelope;
325
+ return {
326
+ // `/graphql` and a small number of product APIs return ordinary JSON
327
+ // instead of the standard v4 envelope. Preserve that document whole so
328
+ // the raw tools cover them too.
329
+ result: isV4Envelope ? envelope.result : envelope,
330
+ resultInfo: isV4Envelope ? envelope.result_info : undefined,
331
+ };
332
+ });
331
333
  }
332
334
  function base64FromBytes(bytes) {
333
335
  let binary = "";
@@ -336,33 +338,34 @@ function base64FromBytes(bytes) {
336
338
  }
337
339
  return btoa(binary);
338
340
  }
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;
341
+ async function callCloudflareContent(send, spec, ctx, responseType) {
342
+ return await send(spec, ctx, async (response) => {
343
+ if (!response.ok) {
344
+ let errors = [];
345
+ try {
346
+ const envelope = (await response.json());
347
+ if (envelope && Array.isArray(envelope.errors))
348
+ errors = envelope.errors;
349
+ }
350
+ catch {
351
+ // A raw or gateway error body has no structured detail to preserve,
352
+ // and one past the byte ceiling has none worth reporting over the
353
+ // status that already failed this call. Either way the throw below
354
+ // classifies the status, so nothing is swallowed into a success.
355
+ }
356
+ throw failureFor(response.status, response.headers, errors);
347
357
  }
348
- catch {
349
- // A raw or gateway error body has no structured detail to preserve.
358
+ const common = {
359
+ contentType: response.headers.get("content-type") ?? "application/octet-stream",
360
+ ...(response.headers.get("etag")
361
+ ? { etag: response.headers.get("etag") }
362
+ : {}),
363
+ };
364
+ if (responseType === "text") {
365
+ return { ...common, text: await response.text() };
350
366
  }
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
- };
367
+ return { ...common, base64: base64FromBytes(await response.bytes()) };
368
+ });
366
369
  }
367
370
  /**
368
371
  * Cloudflare's `result_info` reshaped into the one question an agent actually
@@ -631,8 +634,8 @@ const RAW_INPUT_PROPERTY = {
631
634
  * an out-of-range value with a 400, so each caller passes its own. Encoding
632
635
  * them in the schema turns a wasted round trip into a local repair — but only
633
636
  * 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:
637
+ * range, because `api()` refuses an out-of-range `perPage` locally and an
638
+ * agent deserves to know whether the wall it hit is the API's or ours:
636
639
  *
637
640
  * - `"cloudflare"` — the schema's own documented minimum and maximum.
638
641
  * - `"clamped"` — Cloudflare accepts more; this connection caps it lower.
@@ -663,6 +666,24 @@ function pagingInputProperties(minPerPage, maxPerPage, options = {}) {
663
666
  },
664
667
  };
665
668
  }
669
+ /**
670
+ * The second pagination convention, and the schemas say so.
671
+ *
672
+ * Rulesets, KV keys, and both R2 listings page by cursor rather than by page
673
+ * number, because that is how Cloudflare built them. The guide names the four,
674
+ * but an agent reading one tool's schema should not have to fetch the guide to
675
+ * learn that the `page` object it expected is not coming: absence of
676
+ * `nextCursor` is the loop condition here, and it is stated on both ends
677
+ * ([#342](https://github.com/zackbart/connecta/issues/342)).
678
+ */
679
+ const CURSOR_INPUT_PROPERTY = {
680
+ type: "string",
681
+ description: "Opaque cursor from a previous call's nextCursor. This endpoint pages by cursor, not page number.",
682
+ };
683
+ const NEXT_CURSOR_OUTPUT_PROPERTY = {
684
+ type: "string",
685
+ description: "Pass back as `cursor` to continue. Absent when the listing is complete — this is the only signal; there is no page object.",
686
+ };
666
687
  function listOutputSchema(key, item) {
667
688
  return {
668
689
  type: "object",
@@ -928,7 +949,7 @@ const OPEN_OBJECT_OUTPUT_SCHEMA = {
928
949
  };
929
950
  const QUERY_INPUT_PROPERTY = {
930
951
  type: "array",
931
- description: "Optional query parameters as name/value pairs. Each parameter name may appear once.",
952
+ description: "Query parameters as name/value pairs; each name may appear once.",
932
953
  items: {
933
954
  type: "object",
934
955
  properties: {
@@ -939,9 +960,14 @@ const QUERY_INPUT_PROPERTY = {
939
960
  additionalProperties: false,
940
961
  },
941
962
  };
963
+ // Shared by all three escape hatches, and the compact renderer inlines it
964
+ // three times over. The refused-header list is the connector's boundary rather
965
+ // than a fact the caller composes with, so it lives in the usage guide; keeping
966
+ // it here pushed cloudflare_api_upload's compact input past the 1,024-byte
967
+ // budget ([#342](https://github.com/zackbart/connecta/issues/342)).
942
968
  const HEADERS_INPUT_PROPERTY = {
943
969
  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.",
970
+ description: "Endpoint headers as name/value pairs, e.g. cf-r2-jurisdiction or Range. Connector-owned headers are refused.",
945
971
  items: {
946
972
  type: "object",
947
973
  properties: {
@@ -988,7 +1014,7 @@ const R2_OBJECT_SCHEMA = {
988
1014
  required: ["key"],
989
1015
  };
990
1016
  function buildTools(scope, authentication) {
991
- const { base } = scope;
1017
+ const { send } = scope;
992
1018
  const zoneArg = (args) => requireScope(args["zoneId"], scope.zoneId, "zoneId");
993
1019
  const accountArg = (args) => requireScope(args["accountId"], scope.accountId, "accountId");
994
1020
  const readOnly = { readOnlyHint: true, destructiveHint: false };
@@ -1018,7 +1044,7 @@ function buildTools(scope, authentication) {
1018
1044
  required: ["status"],
1019
1045
  },
1020
1046
  handler: async (_args, ctx) => {
1021
- const { result } = await callCloudflare(base, { method: "GET", path: "/user/tokens/verify" }, ctx);
1047
+ const { result } = await callCloudflare(send, { method: "GET", path: "/user/tokens/verify" }, ctx);
1022
1048
  const token = asRecord(result);
1023
1049
  return {
1024
1050
  id: token["id"],
@@ -1055,14 +1081,14 @@ function buildTools(scope, authentication) {
1055
1081
  required: ["email", "status"],
1056
1082
  },
1057
1083
  handler: async (_args, ctx) => {
1058
- const { result } = await callCloudflare(base, { method: "GET", path: "/user" }, ctx);
1084
+ const { result } = await callCloudflare(send, { method: "GET", path: "/user" }, ctx);
1059
1085
  const user = asRecord(result);
1060
1086
  return { id: user["id"], email: user["email"], status: "active" };
1061
1087
  },
1062
1088
  },
1063
1089
  {
1064
1090
  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.",
1091
+ 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
1092
  annotations: readOnly,
1067
1093
  inputSchema: {
1068
1094
  type: "object",
@@ -1111,9 +1137,9 @@ function buildTools(scope, authentication) {
1111
1137
  ...(headers !== undefined ? { headers } : {}),
1112
1138
  };
1113
1139
  if (responseType === "text" || responseType === "base64") {
1114
- return await callCloudflareContent(base, spec, ctx, responseType);
1140
+ return await callCloudflareContent(send, spec, ctx, responseType);
1115
1141
  }
1116
- const { result, resultInfo } = await callCloudflare(base, spec, ctx);
1142
+ const { result, resultInfo } = await callCloudflare(send, spec, ctx);
1117
1143
  return {
1118
1144
  result,
1119
1145
  ...(resultInfo !== undefined ? { resultInfo } : {}),
@@ -1122,7 +1148,7 @@ function buildTools(scope, authentication) {
1122
1148
  },
1123
1149
  {
1124
1150
  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.",
1151
+ 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
1152
  annotations: { readOnlyHint: false, destructiveHint: true },
1127
1153
  inputSchema: {
1128
1154
  type: "object",
@@ -1164,7 +1190,7 @@ function buildTools(scope, authentication) {
1164
1190
  const method = String(args["method"]);
1165
1191
  const query = queryFromArgs(args["query"]);
1166
1192
  const headers = headersFromArgs(args["headers"]);
1167
- const { result, resultInfo } = await callCloudflare(base, {
1193
+ const { result, resultInfo } = await callCloudflare(send, {
1168
1194
  method,
1169
1195
  path: cloudflareApiPath(args["path"]),
1170
1196
  ...(query !== undefined ? { query } : {}),
@@ -1187,31 +1213,31 @@ function buildTools(scope, authentication) {
1187
1213
  method: {
1188
1214
  type: "string",
1189
1215
  enum: ["POST", "PUT"],
1190
- description: "HTTP upload method required by the Cloudflare endpoint.",
1216
+ description: "Upload method the Cloudflare endpoint requires.",
1191
1217
  },
1192
1218
  path: {
1193
1219
  type: "string",
1194
1220
  minLength: 1,
1195
- description: "Relative path below /client/v4, beginning with '/'. Do not include a query string.",
1221
+ description: "Path below /client/v4, beginning with '/'. No query string.",
1196
1222
  },
1197
1223
  query: QUERY_INPUT_PROPERTY,
1198
1224
  headers: HEADERS_INPUT_PROPERTY,
1199
1225
  contentType: {
1200
1226
  type: "string",
1201
1227
  minLength: 1,
1202
- description: "Content-Type for a raw text/base64 body. Omit for multipart because fetch supplies the boundary.",
1228
+ description: "Content-Type for a raw text or base64 body. Omit for multipart.",
1203
1229
  },
1204
1230
  textBody: {
1205
1231
  type: "string",
1206
- description: "Raw UTF-8 request body. Mutually exclusive with base64Body and multipart fields/files.",
1232
+ description: "Raw UTF-8 body. Exclusive with base64Body and fields/files.",
1207
1233
  },
1208
1234
  base64Body: {
1209
1235
  type: "string",
1210
- description: "Base64-encoded request bytes. Mutually exclusive with textBody and multipart fields/files.",
1236
+ description: "Base64-encoded body bytes. Exclusive with textBody and fields/files.",
1211
1237
  },
1212
1238
  fields: {
1213
1239
  type: "array",
1214
- description: "String fields for a multipart/form-data request.",
1240
+ description: "String fields of a multipart/form-data request.",
1215
1241
  items: {
1216
1242
  type: "object",
1217
1243
  properties: {
@@ -1226,7 +1252,7 @@ function buildTools(scope, authentication) {
1226
1252
  },
1227
1253
  files: {
1228
1254
  type: "array",
1229
- description: "File parts for multipart/form-data. Each file needs exactly one of text or base64.",
1255
+ description: "Multipart file parts. Each needs exactly one of text or base64.",
1230
1256
  items: {
1231
1257
  type: "object",
1232
1258
  properties: {
@@ -1257,7 +1283,7 @@ function buildTools(scope, authentication) {
1257
1283
  const query = queryFromArgs(args["query"]);
1258
1284
  const headers = headersFromArgs(args["headers"]);
1259
1285
  const upload = uploadBody(args);
1260
- const { result } = await callCloudflare(base, {
1286
+ const { result } = await callCloudflare(send, {
1261
1287
  method: String(args["method"]),
1262
1288
  path: cloudflareApiPath(args["path"]),
1263
1289
  ...(query !== undefined ? { query } : {}),
@@ -1288,7 +1314,7 @@ function buildTools(scope, authentication) {
1288
1314
  },
1289
1315
  outputSchema: listOutputSchema("accounts", ACCOUNT_SCHEMA),
1290
1316
  handler: async (args, ctx) => {
1291
- const { result, resultInfo } = await callCloudflare(base, {
1317
+ const { result, resultInfo } = await callCloudflare(send, {
1292
1318
  method: "GET",
1293
1319
  path: "/accounts",
1294
1320
  query: {
@@ -1333,7 +1359,7 @@ function buildTools(scope, authentication) {
1333
1359
  },
1334
1360
  outputSchema: listOutputSchema("zones", ZONE_SCHEMA),
1335
1361
  handler: async (args, ctx) => {
1336
- const { result, resultInfo } = await callCloudflare(base, {
1362
+ const { result, resultInfo } = await callCloudflare(send, {
1337
1363
  method: "GET",
1338
1364
  path: "/zones",
1339
1365
  query: {
@@ -1371,7 +1397,7 @@ function buildTools(scope, authentication) {
1371
1397
  },
1372
1398
  outputSchema: ZONE_SCHEMA,
1373
1399
  handler: async (args, ctx) => {
1374
- const { result } = await callCloudflare(base, { method: "GET", path: `/zones/${encodeURIComponent(zoneArg(args))}` }, ctx);
1400
+ const { result } = await callCloudflare(send, { method: "GET", path: `/zones/${encodeURIComponent(zoneArg(args))}` }, ctx);
1375
1401
  return args["raw"] === true ? result : projectZone(result);
1376
1402
  },
1377
1403
  },
@@ -1389,7 +1415,7 @@ function buildTools(scope, authentication) {
1389
1415
  },
1390
1416
  outputSchema: listOutputSchema("settings", OPEN_OBJECT_OUTPUT_SCHEMA),
1391
1417
  handler: async (args, ctx) => {
1392
- const { result, resultInfo } = await callCloudflare(base, {
1418
+ const { result, resultInfo } = await callCloudflare(send, {
1393
1419
  method: "GET",
1394
1420
  path: `/zones/${encodePathSegment(zoneArg(args))}/settings`,
1395
1421
  }, ctx);
@@ -1415,7 +1441,7 @@ function buildTools(scope, authentication) {
1415
1441
  },
1416
1442
  outputSchema: OPEN_OBJECT_OUTPUT_SCHEMA,
1417
1443
  handler: async (args, ctx) => {
1418
- const { result } = await callCloudflare(base, {
1444
+ const { result } = await callCloudflare(send, {
1419
1445
  method: "GET",
1420
1446
  path: `/zones/${encodePathSegment(zoneArg(args))}/settings/${encodePathSegment(requireString(args, "settingId"))}`,
1421
1447
  }, ctx);
@@ -1446,7 +1472,7 @@ function buildTools(scope, authentication) {
1446
1472
  },
1447
1473
  outputSchema: OPEN_OBJECT_OUTPUT_SCHEMA,
1448
1474
  handler: async (args, ctx) => {
1449
- const { result } = await callCloudflare(base, {
1475
+ const { result } = await callCloudflare(send, {
1450
1476
  method: "PATCH",
1451
1477
  path: `/zones/${encodePathSegment(zoneArg(args))}/settings/${encodePathSegment(requireString(args, "settingId"))}`,
1452
1478
  body: { value: args["value"] },
@@ -1468,10 +1494,7 @@ function buildTools(scope, authentication) {
1468
1494
  maximum: 50,
1469
1495
  description: "Rulesets per request, 1 to 50.",
1470
1496
  },
1471
- cursor: {
1472
- type: "string",
1473
- description: "Opaque cursor returned as nextCursor by the previous call.",
1474
- },
1497
+ cursor: CURSOR_INPUT_PROPERTY,
1475
1498
  },
1476
1499
  required: scopeRequired("zoneId", scope.zoneId),
1477
1500
  additionalProperties: false,
@@ -1480,12 +1503,12 @@ function buildTools(scope, authentication) {
1480
1503
  type: "object",
1481
1504
  properties: {
1482
1505
  rulesets: { type: "array", items: OPEN_OBJECT_OUTPUT_SCHEMA },
1483
- nextCursor: { type: "string" },
1506
+ nextCursor: NEXT_CURSOR_OUTPUT_PROPERTY,
1484
1507
  },
1485
1508
  required: ["rulesets"],
1486
1509
  },
1487
1510
  handler: async (args, ctx) => {
1488
- const { result, resultInfo } = await callCloudflare(base, {
1511
+ const { result, resultInfo } = await callCloudflare(send, {
1489
1512
  method: "GET",
1490
1513
  path: `/zones/${encodePathSegment(zoneArg(args))}/rulesets`,
1491
1514
  query: {
@@ -1521,7 +1544,7 @@ function buildTools(scope, authentication) {
1521
1544
  },
1522
1545
  outputSchema: OPEN_OBJECT_OUTPUT_SCHEMA,
1523
1546
  handler: async (args, ctx) => {
1524
- const { result } = await callCloudflare(base, {
1547
+ const { result } = await callCloudflare(send, {
1525
1548
  method: "GET",
1526
1549
  path: `/zones/${encodePathSegment(zoneArg(args))}/rulesets/${encodePathSegment(requireString(args, "rulesetId"))}`,
1527
1550
  }, ctx);
@@ -1573,7 +1596,7 @@ function buildTools(scope, authentication) {
1573
1596
  },
1574
1597
  outputSchema: listOutputSchema("records", DNS_RECORD_SCHEMA),
1575
1598
  handler: async (args, ctx) => {
1576
- const { result, resultInfo } = await callCloudflare(base, {
1599
+ const { result, resultInfo } = await callCloudflare(send, {
1577
1600
  method: "GET",
1578
1601
  path: `/zones/${encodeURIComponent(zoneArg(args))}/dns_records`,
1579
1602
  query: {
@@ -1613,7 +1636,7 @@ function buildTools(scope, authentication) {
1613
1636
  },
1614
1637
  outputSchema: DNS_RECORD_SCHEMA,
1615
1638
  handler: async (args, ctx) => {
1616
- const { result } = await callCloudflare(base, {
1639
+ const { result } = await callCloudflare(send, {
1617
1640
  method: "GET",
1618
1641
  path: `/zones/${encodeURIComponent(zoneArg(args))}/dns_records/${encodeURIComponent(String(args["recordId"]))}`,
1619
1642
  }, ctx);
@@ -1644,7 +1667,7 @@ function buildTools(scope, authentication) {
1644
1667
  required: ["id"],
1645
1668
  }),
1646
1669
  handler: async (args, ctx) => {
1647
- const { result, resultInfo } = await callCloudflare(base, {
1670
+ const { result, resultInfo } = await callCloudflare(send, {
1648
1671
  method: "GET",
1649
1672
  path: `/accounts/${encodeURIComponent(accountArg(args))}/workers/scripts`,
1650
1673
  }, ctx);
@@ -1675,7 +1698,7 @@ function buildTools(scope, authentication) {
1675
1698
  },
1676
1699
  outputSchema: OPEN_OBJECT_OUTPUT_SCHEMA,
1677
1700
  handler: async (args, ctx) => {
1678
- const { result } = await callCloudflare(base, {
1701
+ const { result } = await callCloudflare(send, {
1679
1702
  method: "GET",
1680
1703
  path: `/accounts/${encodePathSegment(accountArg(args))}/workers/scripts/${encodePathSegment(requireString(args, "scriptName"))}/settings`,
1681
1704
  }, ctx);
@@ -1701,7 +1724,7 @@ function buildTools(scope, authentication) {
1701
1724
  },
1702
1725
  outputSchema: listOutputSchema("deployments", OPEN_OBJECT_OUTPUT_SCHEMA),
1703
1726
  handler: async (args, ctx) => {
1704
- const { result } = await callCloudflare(base, {
1727
+ const { result } = await callCloudflare(send, {
1705
1728
  method: "GET",
1706
1729
  path: `/accounts/${encodePathSegment(accountArg(args))}/workers/scripts/${encodePathSegment(requireString(args, "scriptName"))}/deployments`,
1707
1730
  }, ctx);
@@ -1740,7 +1763,7 @@ function buildTools(scope, authentication) {
1740
1763
  },
1741
1764
  outputSchema: OPEN_OBJECT_OUTPUT_SCHEMA,
1742
1765
  handler: async (args, ctx) => {
1743
- const { result } = await callCloudflare(base, {
1766
+ const { result } = await callCloudflare(send, {
1744
1767
  method: "GET",
1745
1768
  path: `/accounts/${encodePathSegment(accountArg(args))}/workers/scripts/${encodePathSegment(requireString(args, "scriptName"))}/deployments/${encodePathSegment(requireString(args, "deploymentId"))}`,
1746
1769
  }, ctx);
@@ -1778,7 +1801,7 @@ function buildTools(scope, authentication) {
1778
1801
  },
1779
1802
  handler: async (args, ctx) => {
1780
1803
  const scriptName = requireString(args, "scriptName");
1781
- await callCloudflare(base, {
1804
+ await callCloudflare(send, {
1782
1805
  method: "DELETE",
1783
1806
  path: `/accounts/${encodePathSegment(accountArg(args))}/workers/scripts/${encodePathSegment(scriptName)}`,
1784
1807
  query: { force: optionalBoolean(args, "force") },
@@ -1810,7 +1833,7 @@ function buildTools(scope, authentication) {
1810
1833
  required: ["id", "title"],
1811
1834
  }),
1812
1835
  handler: async (args, ctx) => {
1813
- const { result, resultInfo } = await callCloudflare(base, {
1836
+ const { result, resultInfo } = await callCloudflare(send, {
1814
1837
  method: "GET",
1815
1838
  path: `/accounts/${encodeURIComponent(accountArg(args))}/storage/kv/namespaces`,
1816
1839
  query: {
@@ -1845,7 +1868,7 @@ function buildTools(scope, authentication) {
1845
1868
  },
1846
1869
  outputSchema: OPEN_OBJECT_OUTPUT_SCHEMA,
1847
1870
  handler: async (args, ctx) => {
1848
- const { result } = await callCloudflare(base, {
1871
+ const { result } = await callCloudflare(send, {
1849
1872
  method: "GET",
1850
1873
  path: `/accounts/${encodePathSegment(accountArg(args))}/storage/kv/namespaces/${encodePathSegment(requireString(args, "namespaceId"))}`,
1851
1874
  }, ctx);
@@ -1872,7 +1895,7 @@ function buildTools(scope, authentication) {
1872
1895
  },
1873
1896
  outputSchema: OPEN_OBJECT_OUTPUT_SCHEMA,
1874
1897
  handler: async (args, ctx) => {
1875
- const { result } = await callCloudflare(base, {
1898
+ const { result } = await callCloudflare(send, {
1876
1899
  method: "POST",
1877
1900
  path: `/accounts/${encodePathSegment(accountArg(args))}/storage/kv/namespaces`,
1878
1901
  body: { title: requireString(args, "title") },
@@ -1909,7 +1932,7 @@ function buildTools(scope, authentication) {
1909
1932
  },
1910
1933
  outputSchema: OPEN_OBJECT_OUTPUT_SCHEMA,
1911
1934
  handler: async (args, ctx) => {
1912
- const { result } = await callCloudflare(base, {
1935
+ const { result } = await callCloudflare(send, {
1913
1936
  method: "PUT",
1914
1937
  path: `/accounts/${encodePathSegment(accountArg(args))}/storage/kv/namespaces/${encodePathSegment(requireString(args, "namespaceId"))}`,
1915
1938
  body: { title: requireString(args, "title") },
@@ -1944,7 +1967,7 @@ function buildTools(scope, authentication) {
1944
1967
  },
1945
1968
  handler: async (args, ctx) => {
1946
1969
  const namespaceId = requireString(args, "namespaceId");
1947
- await callCloudflare(base, {
1970
+ await callCloudflare(send, {
1948
1971
  method: "DELETE",
1949
1972
  path: `/accounts/${encodePathSegment(accountArg(args))}/storage/kv/namespaces/${encodePathSegment(namespaceId)}`,
1950
1973
  }, ctx);
@@ -1974,10 +1997,7 @@ function buildTools(scope, authentication) {
1974
1997
  maximum: 1000,
1975
1998
  description: "Keys per request, 10 to 1000. Defaults to 1000.",
1976
1999
  },
1977
- cursor: {
1978
- type: "string",
1979
- description: "Opaque cursor returned as nextCursor by the previous call.",
1980
- },
2000
+ cursor: CURSOR_INPUT_PROPERTY,
1981
2001
  },
1982
2002
  required: [...scopeRequired("accountId", scope.accountId), "namespaceId"],
1983
2003
  additionalProperties: false,
@@ -1986,12 +2006,12 @@ function buildTools(scope, authentication) {
1986
2006
  type: "object",
1987
2007
  properties: {
1988
2008
  keys: { type: "array", items: OPEN_OBJECT_OUTPUT_SCHEMA },
1989
- nextCursor: { type: "string" },
2009
+ nextCursor: NEXT_CURSOR_OUTPUT_PROPERTY,
1990
2010
  },
1991
2011
  required: ["keys"],
1992
2012
  },
1993
2013
  handler: async (args, ctx) => {
1994
- const { result, resultInfo } = await callCloudflare(base, {
2014
+ const { result, resultInfo } = await callCloudflare(send, {
1995
2015
  method: "GET",
1996
2016
  path: `/accounts/${encodePathSegment(accountArg(args))}/storage/kv/namespaces/${encodePathSegment(requireString(args, "namespaceId"))}/keys`,
1997
2017
  query: {
@@ -2048,7 +2068,7 @@ function buildTools(scope, authentication) {
2048
2068
  },
2049
2069
  outputSchema: OPEN_OBJECT_OUTPUT_SCHEMA,
2050
2070
  handler: async (args, ctx) => {
2051
- const { result } = await callCloudflare(base, {
2071
+ const { result } = await callCloudflare(send, {
2052
2072
  method: "POST",
2053
2073
  path: `/accounts/${encodePathSegment(accountArg(args))}/storage/kv/namespaces/${encodePathSegment(requireString(args, "namespaceId"))}/bulk/get`,
2054
2074
  body: {
@@ -2083,12 +2103,34 @@ function buildTools(scope, authentication) {
2083
2103
  items: {
2084
2104
  type: "object",
2085
2105
  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" },
2106
+ key: {
2107
+ type: "string",
2108
+ minLength: 1,
2109
+ maxLength: 512,
2110
+ description: "Key name, up to Cloudflare's 512 bytes.",
2111
+ },
2112
+ value: {
2113
+ type: "string",
2114
+ maxLength: 26_214_400,
2115
+ description: "Value, up to Cloudflare's 25 MiB.",
2116
+ },
2117
+ expiration: {
2118
+ type: "number",
2119
+ description: "Absolute expiry as a Unix timestamp in seconds.",
2120
+ },
2121
+ expiration_ttl: {
2122
+ type: "number",
2123
+ minimum: 60,
2124
+ description: "Relative expiry in seconds; Cloudflare's floor is 60.",
2125
+ },
2126
+ metadata: {
2127
+ type: ["object", "array", "string", "number", "boolean", "null"],
2128
+ description: "JSON metadata returned beside the key by list_kv_keys.",
2129
+ },
2130
+ base64: {
2131
+ type: "boolean",
2132
+ description: "Treat value as base64 and store the decoded bytes.",
2133
+ },
2092
2134
  },
2093
2135
  required: ["key", "value"],
2094
2136
  additionalProperties: false,
@@ -2104,7 +2146,7 @@ function buildTools(scope, authentication) {
2104
2146
  },
2105
2147
  outputSchema: OPEN_OBJECT_OUTPUT_SCHEMA,
2106
2148
  handler: async (args, ctx) => {
2107
- const { result } = await callCloudflare(base, {
2149
+ const { result } = await callCloudflare(send, {
2108
2150
  method: "PUT",
2109
2151
  path: `/accounts/${encodePathSegment(accountArg(args))}/storage/kv/namespaces/${encodePathSegment(requireString(args, "namespaceId"))}/bulk`,
2110
2152
  body: args["entries"],
@@ -2142,7 +2184,7 @@ function buildTools(scope, authentication) {
2142
2184
  },
2143
2185
  outputSchema: OPEN_OBJECT_OUTPUT_SCHEMA,
2144
2186
  handler: async (args, ctx) => {
2145
- const { result } = await callCloudflare(base, {
2187
+ const { result } = await callCloudflare(send, {
2146
2188
  method: "POST",
2147
2189
  path: `/accounts/${encodePathSegment(accountArg(args))}/storage/kv/namespaces/${encodePathSegment(requireString(args, "namespaceId"))}/bulk/delete`,
2148
2190
  body: args["keys"],
@@ -2168,10 +2210,7 @@ function buildTools(scope, authentication) {
2168
2210
  maximum: 1000,
2169
2211
  description: "Buckets per request, 1 to 1000. Defaults to 20.",
2170
2212
  },
2171
- cursor: {
2172
- type: "string",
2173
- description: "Opaque cursor from a previous call's nextCursor. R2 paginates by cursor, not page number.",
2174
- },
2213
+ cursor: CURSOR_INPUT_PROPERTY,
2175
2214
  jurisdiction: R2_JURISDICTION_PROPERTY,
2176
2215
  raw: RAW_INPUT_PROPERTY,
2177
2216
  },
@@ -2195,15 +2234,12 @@ function buildTools(scope, authentication) {
2195
2234
  required: ["name"],
2196
2235
  },
2197
2236
  },
2198
- nextCursor: {
2199
- type: "string",
2200
- description: "Pass back as `cursor` to continue. Absent when the listing is complete.",
2201
- },
2237
+ nextCursor: NEXT_CURSOR_OUTPUT_PROPERTY,
2202
2238
  },
2203
2239
  required: ["buckets"],
2204
2240
  },
2205
2241
  handler: async (args, ctx) => {
2206
- const { result, resultInfo } = await callCloudflare(base, {
2242
+ const { result, resultInfo } = await callCloudflare(send, {
2207
2243
  method: "GET",
2208
2244
  path: `/accounts/${encodeURIComponent(accountArg(args))}/r2/buckets`,
2209
2245
  query: {
@@ -2243,7 +2279,7 @@ function buildTools(scope, authentication) {
2243
2279
  },
2244
2280
  outputSchema: R2_BUCKET_SCHEMA,
2245
2281
  handler: async (args, ctx) => {
2246
- const { result } = await callCloudflare(base, {
2282
+ const { result } = await callCloudflare(send, {
2247
2283
  method: "GET",
2248
2284
  path: `/accounts/${encodePathSegment(accountArg(args))}/r2/buckets/${encodePathSegment(requireString(args, "bucketName"))}`,
2249
2285
  headers: r2Headers(args),
@@ -2277,7 +2313,7 @@ function buildTools(scope, authentication) {
2277
2313
  },
2278
2314
  outputSchema: R2_BUCKET_SCHEMA,
2279
2315
  handler: async (args, ctx) => {
2280
- const { result } = await callCloudflare(base, {
2316
+ const { result } = await callCloudflare(send, {
2281
2317
  method: "POST",
2282
2318
  path: `/accounts/${encodePathSegment(accountArg(args))}/r2/buckets`,
2283
2319
  headers: r2Headers(args),
@@ -2319,7 +2355,7 @@ function buildTools(scope, authentication) {
2319
2355
  },
2320
2356
  outputSchema: R2_BUCKET_SCHEMA,
2321
2357
  handler: async (args, ctx) => {
2322
- const { result } = await callCloudflare(base, {
2358
+ const { result } = await callCloudflare(send, {
2323
2359
  method: "PATCH",
2324
2360
  path: `/accounts/${encodePathSegment(accountArg(args))}/r2/buckets/${encodePathSegment(requireString(args, "bucketName"))}`,
2325
2361
  headers: {
@@ -2354,7 +2390,7 @@ function buildTools(scope, authentication) {
2354
2390
  },
2355
2391
  handler: async (args, ctx) => {
2356
2392
  const bucketName = requireString(args, "bucketName");
2357
- await callCloudflare(base, {
2393
+ await callCloudflare(send, {
2358
2394
  method: "DELETE",
2359
2395
  path: `/accounts/${encodePathSegment(accountArg(args))}/r2/buckets/${encodePathSegment(bucketName)}`,
2360
2396
  headers: r2Headers(args),
@@ -2392,10 +2428,7 @@ function buildTools(scope, authentication) {
2392
2428
  maximum: 1000,
2393
2429
  description: "Objects per request, 1 to 1000.",
2394
2430
  },
2395
- cursor: {
2396
- type: "string",
2397
- description: "Opaque cursor returned as nextCursor by the previous call.",
2398
- },
2431
+ cursor: CURSOR_INPUT_PROPERTY,
2399
2432
  },
2400
2433
  required: [...scopeRequired("accountId", scope.accountId), "bucketName"],
2401
2434
  additionalProperties: false,
@@ -2405,13 +2438,13 @@ function buildTools(scope, authentication) {
2405
2438
  properties: {
2406
2439
  objects: { type: "array", items: R2_OBJECT_SCHEMA },
2407
2440
  commonPrefixes: { type: "array", items: { type: "string" } },
2408
- nextCursor: { type: "string" },
2441
+ nextCursor: NEXT_CURSOR_OUTPUT_PROPERTY,
2409
2442
  truncated: { type: "boolean" },
2410
2443
  },
2411
2444
  required: ["objects", "truncated"],
2412
2445
  },
2413
2446
  handler: async (args, ctx) => {
2414
- const { result, resultInfo } = await callCloudflare(base, {
2447
+ const { result, resultInfo } = await callCloudflare(send, {
2415
2448
  method: "GET",
2416
2449
  path: `/accounts/${encodePathSegment(accountArg(args))}/r2/buckets/${encodePathSegment(requireString(args, "bucketName"))}/objects`,
2417
2450
  headers: r2Headers(args),
@@ -2469,7 +2502,7 @@ function buildTools(scope, authentication) {
2469
2502
  },
2470
2503
  handler: async (args, ctx) => {
2471
2504
  const objectKey = requireString(args, "objectKey");
2472
- await callCloudflare(base, {
2505
+ await callCloudflare(send, {
2473
2506
  method: "DELETE",
2474
2507
  path: `/accounts/${encodePathSegment(accountArg(args))}/r2/buckets/${encodePathSegment(requireString(args, "bucketName"))}/objects/${encodeObjectKey(objectKey)}`,
2475
2508
  headers: r2Headers(args),
@@ -2477,25 +2510,17 @@ function buildTools(scope, authentication) {
2477
2510
  return { deleted: true, objectKey };
2478
2511
  },
2479
2512
  },
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
- },
2513
+ // No `get_r2_metrics`, `set_r2_cors`, or `delete_r2_cors` here on purpose.
2514
+ // The #350 measurement found `get_r2_metrics` an unprojected,
2515
+ // output-schema-less wrapper around a path, and `set_r2_cors` carrying a
2516
+ // free-form `additionalProperties: true` rule body, so its schema did not
2517
+ // validate the part of the call that actually fails. `delete_r2_cors`
2518
+ // measured clean — a fixed `{deleted}` confirmation behind a closed output
2519
+ // schema and went anyway, as the other half of one policy pair: with the
2520
+ // write unnamed, a named delete would change a CORS policy through a
2521
+ // different route than setting it does. Reading a policy is still named;
2522
+ // changing one takes the approval-gated raw route, exactly as
2523
+ // structured-data DNS records already do.
2499
2524
  {
2500
2525
  name: "get_r2_cors",
2501
2526
  description: "Get the browser CORS rules configured on an R2 bucket.",
@@ -2512,7 +2537,7 @@ function buildTools(scope, authentication) {
2512
2537
  },
2513
2538
  outputSchema: OPEN_OBJECT_OUTPUT_SCHEMA,
2514
2539
  handler: async (args, ctx) => {
2515
- const { result } = await callCloudflare(base, {
2540
+ const { result } = await callCloudflare(send, {
2516
2541
  method: "GET",
2517
2542
  path: `/accounts/${encodePathSegment(accountArg(args))}/r2/buckets/${encodePathSegment(requireString(args, "bucketName"))}/cors`,
2518
2543
  headers: r2Headers(args),
@@ -2520,65 +2545,6 @@ function buildTools(scope, authentication) {
2520
2545
  return asRecord(result);
2521
2546
  },
2522
2547
  },
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
2548
  {
2583
2549
  name: "list_pages_projects",
2584
2550
  description: "List Cloudflare Pages projects in an account, with their production branch and latest deployment.",
@@ -2614,7 +2580,7 @@ function buildTools(scope, authentication) {
2614
2580
  required: ["name"],
2615
2581
  }),
2616
2582
  handler: async (args, ctx) => {
2617
- const { result, resultInfo } = await callCloudflare(base, {
2583
+ const { result, resultInfo } = await callCloudflare(send, {
2618
2584
  method: "GET",
2619
2585
  path: `/accounts/${encodeURIComponent(accountArg(args))}/pages/projects`,
2620
2586
  query: {
@@ -2650,7 +2616,7 @@ function buildTools(scope, authentication) {
2650
2616
  },
2651
2617
  outputSchema: OPEN_OBJECT_OUTPUT_SCHEMA,
2652
2618
  handler: async (args, ctx) => {
2653
- const { result } = await callCloudflare(base, {
2619
+ const { result } = await callCloudflare(send, {
2654
2620
  method: "GET",
2655
2621
  path: `/accounts/${encodePathSegment(accountArg(args))}/pages/projects/${encodePathSegment(requireString(args, "projectName"))}`,
2656
2622
  }, ctx);
@@ -2682,7 +2648,7 @@ function buildTools(scope, authentication) {
2682
2648
  },
2683
2649
  outputSchema: listOutputSchema("deployments", OPEN_OBJECT_OUTPUT_SCHEMA),
2684
2650
  handler: async (args, ctx) => {
2685
- const { result, resultInfo } = await callCloudflare(base, {
2651
+ const { result, resultInfo } = await callCloudflare(send, {
2686
2652
  method: "GET",
2687
2653
  path: `/accounts/${encodePathSegment(accountArg(args))}/pages/projects/${encodePathSegment(requireString(args, "projectName"))}/deployments`,
2688
2654
  query: {
@@ -2726,7 +2692,7 @@ function buildTools(scope, authentication) {
2726
2692
  },
2727
2693
  outputSchema: OPEN_OBJECT_OUTPUT_SCHEMA,
2728
2694
  handler: async (args, ctx) => {
2729
- const { result } = await callCloudflare(base, {
2695
+ const { result } = await callCloudflare(send, {
2730
2696
  method: "GET",
2731
2697
  path: `/accounts/${encodePathSegment(accountArg(args))}/pages/projects/${encodePathSegment(requireString(args, "projectName"))}/deployments/${encodePathSegment(requireString(args, "deploymentId"))}`,
2732
2698
  }, ctx);
@@ -2749,7 +2715,7 @@ function buildTools(scope, authentication) {
2749
2715
  },
2750
2716
  outputSchema: OPEN_OBJECT_OUTPUT_SCHEMA,
2751
2717
  handler: async (args, ctx) => {
2752
- const { result } = await callCloudflare(base, {
2718
+ const { result } = await callCloudflare(send, {
2753
2719
  method: "POST",
2754
2720
  path: `/accounts/${encodePathSegment(accountArg(args))}/pages/projects/${encodePathSegment(requireString(args, "projectName"))}/deployments/${encodePathSegment(requireString(args, "deploymentId"))}/retry`,
2755
2721
  }, ctx);
@@ -2772,7 +2738,7 @@ function buildTools(scope, authentication) {
2772
2738
  },
2773
2739
  outputSchema: OPEN_OBJECT_OUTPUT_SCHEMA,
2774
2740
  handler: async (args, ctx) => {
2775
- const { result } = await callCloudflare(base, {
2741
+ const { result } = await callCloudflare(send, {
2776
2742
  method: "POST",
2777
2743
  path: `/accounts/${encodePathSegment(accountArg(args))}/pages/projects/${encodePathSegment(requireString(args, "projectName"))}/deployments/${encodePathSegment(requireString(args, "deploymentId"))}/rollback`,
2778
2744
  }, ctx);
@@ -2796,7 +2762,7 @@ function buildTools(scope, authentication) {
2796
2762
  outputSchema: { type: "object", properties: { deleted: { type: "boolean" }, deploymentId: { type: "string" } }, required: ["deleted", "deploymentId"] },
2797
2763
  handler: async (args, ctx) => {
2798
2764
  const deploymentId = requireString(args, "deploymentId");
2799
- await callCloudflare(base, {
2765
+ await callCloudflare(send, {
2800
2766
  method: "DELETE",
2801
2767
  path: `/accounts/${encodePathSegment(accountArg(args))}/pages/projects/${encodePathSegment(requireString(args, "projectName"))}/deployments/${encodePathSegment(deploymentId)}`,
2802
2768
  }, ctx);
@@ -2818,7 +2784,7 @@ function buildTools(scope, authentication) {
2818
2784
  },
2819
2785
  outputSchema: listOutputSchema("domains", OPEN_OBJECT_OUTPUT_SCHEMA),
2820
2786
  handler: async (args, ctx) => {
2821
- const { result } = await callCloudflare(base, {
2787
+ const { result } = await callCloudflare(send, {
2822
2788
  method: "GET",
2823
2789
  path: `/accounts/${encodePathSegment(accountArg(args))}/pages/projects/${encodePathSegment(requireString(args, "projectName"))}/domains`,
2824
2790
  }, ctx);
@@ -2841,7 +2807,7 @@ function buildTools(scope, authentication) {
2841
2807
  },
2842
2808
  outputSchema: OPEN_OBJECT_OUTPUT_SCHEMA,
2843
2809
  handler: async (args, ctx) => {
2844
- const { result } = await callCloudflare(base, {
2810
+ const { result } = await callCloudflare(send, {
2845
2811
  method: "POST",
2846
2812
  path: `/accounts/${encodePathSegment(accountArg(args))}/pages/projects/${encodePathSegment(requireString(args, "projectName"))}/domains`,
2847
2813
  body: { name: requireString(args, "domain") },
@@ -2866,7 +2832,7 @@ function buildTools(scope, authentication) {
2866
2832
  outputSchema: { type: "object", properties: { deleted: { type: "boolean" }, domain: { type: "string" } }, required: ["deleted", "domain"] },
2867
2833
  handler: async (args, ctx) => {
2868
2834
  const domain = requireString(args, "domain");
2869
- await callCloudflare(base, {
2835
+ await callCloudflare(send, {
2870
2836
  method: "DELETE",
2871
2837
  path: `/accounts/${encodePathSegment(accountArg(args))}/pages/projects/${encodePathSegment(requireString(args, "projectName"))}/domains/${encodePathSegment(domain)}`,
2872
2838
  }, ctx);
@@ -2888,7 +2854,7 @@ function buildTools(scope, authentication) {
2888
2854
  },
2889
2855
  outputSchema: { type: "object", properties: { purged: { type: "boolean" } }, required: ["purged"] },
2890
2856
  handler: async (args, ctx) => {
2891
- await callCloudflare(base, {
2857
+ await callCloudflare(send, {
2892
2858
  method: "POST",
2893
2859
  path: `/accounts/${encodePathSegment(accountArg(args))}/pages/projects/${encodePathSegment(requireString(args, "projectName"))}/purge_build_cache`,
2894
2860
  }, ctx);
@@ -2911,7 +2877,7 @@ function buildTools(scope, authentication) {
2911
2877
  outputSchema: { type: "object", properties: { deleted: { type: "boolean" }, projectName: { type: "string" } }, required: ["deleted", "projectName"] },
2912
2878
  handler: async (args, ctx) => {
2913
2879
  const projectName = requireString(args, "projectName");
2914
- await callCloudflare(base, {
2880
+ await callCloudflare(send, {
2915
2881
  method: "DELETE",
2916
2882
  path: `/accounts/${encodePathSegment(accountArg(args))}/pages/projects/${encodePathSegment(projectName)}`,
2917
2883
  }, ctx);
@@ -2923,7 +2889,7 @@ function buildTools(scope, authentication) {
2923
2889
  // `destructiveHint` stays unset. `readOnlyHint: false` already routes it
2924
2890
  // through call_destructive_tool.
2925
2891
  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.",
2892
+ 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
2893
  annotations: { readOnlyHint: false },
2928
2894
  inputSchema: {
2929
2895
  type: "object",
@@ -2978,7 +2944,7 @@ function buildTools(scope, authentication) {
2978
2944
  },
2979
2945
  outputSchema: DNS_RECORD_SCHEMA,
2980
2946
  handler: async (args, ctx) => {
2981
- const { result } = await callCloudflare(base, {
2947
+ const { result } = await callCloudflare(send, {
2982
2948
  method: "POST",
2983
2949
  path: `/zones/${encodeURIComponent(zoneArg(args))}/dns_records`,
2984
2950
  body: {
@@ -3079,7 +3045,7 @@ function buildTools(scope, authentication) {
3079
3045
  },
3080
3046
  });
3081
3047
  }
3082
- const { result } = await callCloudflare(base, {
3048
+ const { result } = await callCloudflare(send, {
3083
3049
  method: "PATCH",
3084
3050
  path: `/zones/${encodeURIComponent(zoneArg(args))}/dns_records/${encodeURIComponent(String(args["recordId"]))}`,
3085
3051
  body,
@@ -3113,7 +3079,7 @@ function buildTools(scope, authentication) {
3113
3079
  },
3114
3080
  handler: async (args, ctx) => {
3115
3081
  const recordId = String(args["recordId"]);
3116
- await callCloudflare(base, {
3082
+ await callCloudflare(send, {
3117
3083
  method: "DELETE",
3118
3084
  path: `/zones/${encodeURIComponent(zoneArg(args))}/dns_records/${encodeURIComponent(recordId)}`,
3119
3085
  }, ctx);
@@ -3213,7 +3179,7 @@ function buildTools(scope, authentication) {
3213
3179
  });
3214
3180
  }
3215
3181
  const variant = everything ? "everything" : targeted[0];
3216
- await callCloudflare(base, {
3182
+ await callCloudflare(send, {
3217
3183
  method: "POST",
3218
3184
  path: `/zones/${encodeURIComponent(zoneId)}/purge_cache`,
3219
3185
  body: everything
@@ -3246,10 +3212,11 @@ Account purpose: ${purpose}
3246
3212
 
3247
3213
  - ${zoneLine}
3248
3214
  - ${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.
3215
+ - 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
3216
  - 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
3217
  - 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.
3218
+ - Two R2 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\`.
3219
+ - 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
3220
  - 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
3221
  - ${authenticationLine}
3255
3222
  - 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 +3240,7 @@ export function cloudflare(id, options) {
3273
3240
  throw new Error('cloudflare() authentication must be "apiToken" or "globalApiKey".');
3274
3241
  }
3275
3242
  const scope = {
3276
- base: options.baseUrl?.trim() || CLOUDFLARE_API_BASE,
3243
+ send: cloudflareTransport(options.baseUrl?.trim() || CLOUDFLARE_API_BASE),
3277
3244
  accountId: options.accountId?.trim() || undefined,
3278
3245
  zoneId: options.zoneId?.trim() || undefined,
3279
3246
  };
@@ -3282,10 +3249,18 @@ export function cloudflare(id, options) {
3282
3249
  description: `Cloudflare control-plane access for zones, DNS, Workers, KV, R2, Pages, media, email, and other v4 APIs — ${purpose}`,
3283
3250
  credential: credentialConfig(authentication, options.credential),
3284
3251
  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,
3252
+ usageGuide: {
3253
+ content: usageGuide(purpose, scope, options.instructions, authentication),
3254
+ // Explicit rather than derived: the first content line is the zone
3255
+ // scoping rule, which varies per deployment and reads as an instruction
3256
+ // rather than as the routing fact a browsing agent needs.
3257
+ summary: "Zone and account scoping, named-vs-raw routing, two pagination shapes, and lean-vs-raw results.",
3258
+ // Deliberately not `required`. Every named tool's schema is complete
3259
+ // enough to call it correctly on its own, and the scoping convention the
3260
+ // guide carries is repeated on each `zoneId` and `accountId` property —
3261
+ // so forcing the guide into context before every operation would spend
3262
+ // tokens on a sequence the schemas already express.
3263
+ },
3289
3264
  ...(options.maxResultBytes !== undefined
3290
3265
  ? { maxResultBytes: options.maxResultBytes }
3291
3266
  : {}),
@@ -3294,7 +3269,7 @@ export function cloudflare(id, options) {
3294
3269
  ? {
3295
3270
  async testCredential(value, ctx) {
3296
3271
  try {
3297
- const { result } = await callCloudflare(scope.base, { method: "GET", path: "/user/tokens/verify" }, withAuthentication({
3272
+ const { result } = await callCloudflare(scope.send, { method: "GET", path: "/user/tokens/verify" }, withAuthentication({
3298
3273
  ...ctx,
3299
3274
  credential: {
3300
3275
  get: async () => value,
@@ -3317,7 +3292,7 @@ export function cloudflare(id, options) {
3317
3292
  : {
3318
3293
  async testCredentials(values, ctx) {
3319
3294
  try {
3320
- const { result } = await callCloudflare(scope.base, { method: "GET", path: "/user" }, withAuthentication({
3295
+ const { result } = await callCloudflare(scope.send, { method: "GET", path: "/user" }, withAuthentication({
3321
3296
  ...ctx,
3322
3297
  credential: {
3323
3298
  get: async (field) => field ? values[field] ?? null : null,
@@ -3340,4 +3315,3 @@ export function cloudflare(id, options) {
3340
3315
  }),
3341
3316
  });
3342
3317
  }
3343
- //# sourceMappingURL=cloudflare.js.map