@zackbart/connecta 0.24.2 → 0.24.4

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 (97) hide show
  1. package/AGENTS.md +18 -20
  2. package/CHANGELOG.md +205 -1
  3. package/README.md +5 -6
  4. package/dist/auth/bearer.js +2 -0
  5. package/dist/auth/downstream-oauth.d.ts +12 -1
  6. package/dist/auth/downstream-oauth.js +147 -35
  7. package/dist/branding.d.ts +31 -2
  8. package/dist/branding.js +116 -8
  9. package/dist/call-admission.d.ts +4 -0
  10. package/dist/call-admission.js +26 -0
  11. package/dist/catalog-drift.js +9 -4
  12. package/dist/catalog-service.d.ts +2 -0
  13. package/dist/catalog-service.js +25 -8
  14. package/dist/catalog.d.ts +2 -0
  15. package/dist/catalog.js +246 -121
  16. package/dist/connectors/api.d.ts +1 -1
  17. package/dist/connectors/api.js +21 -3
  18. package/dist/connectors/guarded-fetch.d.ts +6 -2
  19. package/dist/connectors/guarded-fetch.js +61 -24
  20. package/dist/connectors/remote-mcp.js +92 -57
  21. package/dist/errors.d.ts +28 -3
  22. package/dist/errors.js +60 -1
  23. package/dist/execute.js +85 -23
  24. package/dist/executor-result.js +3 -1
  25. package/dist/executors/quickjs-child.js +5 -1
  26. package/dist/executors/quickjs-protocol.d.ts +4 -0
  27. package/dist/executors/quickjs-runtime.d.ts +1 -1
  28. package/dist/executors/quickjs-runtime.js +38 -21
  29. package/dist/executors/quickjs.js +68 -27
  30. package/dist/index.d.ts +15 -1
  31. package/dist/index.js +36 -4
  32. package/dist/invocation.js +134 -93
  33. package/dist/mcp-result.js +3 -2
  34. package/dist/meta-tools.js +191 -36
  35. package/dist/operator-ui/generated.js +2 -2
  36. package/dist/operator-ui/view.d.ts +38 -1
  37. package/dist/operator-ui/view.js +71 -0
  38. package/dist/providers/cloudflare.d.ts +14 -2
  39. package/dist/providers/cloudflare.js +107 -16
  40. package/dist/providers/linear.d.ts +26 -4
  41. package/dist/providers/linear.js +19 -4
  42. package/dist/providers/mixpanel.d.ts +16 -3
  43. package/dist/providers/mixpanel.js +13 -2
  44. package/dist/providers/notion.d.ts +8 -1
  45. package/dist/providers/notion.js +83 -10
  46. package/dist/providers/revenuecat.d.ts +30 -4
  47. package/dist/providers/revenuecat.js +42 -4
  48. package/dist/providers/stripe.d.ts +7 -1
  49. package/dist/providers/stripe.js +30 -4
  50. package/dist/providers/vercel.js +11 -1
  51. package/dist/registry.d.ts +22 -2
  52. package/dist/registry.js +101 -13
  53. package/dist/routes/mcp.d.ts +4 -1
  54. package/dist/routes/mcp.js +84 -13
  55. package/dist/routes/oauth.js +4 -0
  56. package/dist/routes/shared.d.ts +1 -0
  57. package/dist/routes/shared.js +4 -4
  58. package/dist/server.js +15 -3
  59. package/dist/skills.js +6 -5
  60. package/dist/storage/file.d.ts +6 -2
  61. package/dist/storage/file.js +312 -34
  62. package/dist/storage/memory.js +12 -1
  63. package/dist/types.d.ts +37 -0
  64. package/dist/ui.js +18 -10
  65. package/dist/validate.js +3 -3
  66. package/dist/version.d.ts +1 -1
  67. package/dist/version.js +1 -1
  68. package/documentation/architecture.md +193 -165
  69. package/documentation/auth.md +199 -145
  70. package/documentation/code-mode.md +433 -316
  71. package/documentation/meta-tools.md +363 -355
  72. package/examples/worker/AGENTS.md +2 -1
  73. package/examples/worker/README.md +12 -10
  74. package/examples/worker/src/index.ts +12 -15
  75. package/package.json +1 -2
  76. package/templates/node/.env.example +3 -3
  77. package/templates/node/AGENTS.md +5 -4
  78. package/templates/node/README.md +2 -1
  79. package/templates/node/package.json +1 -1
  80. package/templates/node/src/index.ts +23 -22
  81. package/documentation/call-admission.md +0 -142
  82. package/documentation/cloudflare.md +0 -471
  83. package/documentation/connector-guides.md +0 -176
  84. package/documentation/connectors.md +0 -389
  85. package/documentation/linear.md +0 -193
  86. package/documentation/mixpanel.md +0 -160
  87. package/documentation/notion.md +0 -308
  88. package/documentation/operations.md +0 -359
  89. package/documentation/operator-ui.md +0 -135
  90. package/documentation/optional-modules-upgrade.md +0 -243
  91. package/documentation/provider-conventions.md +0 -722
  92. package/documentation/request-admission.md +0 -170
  93. package/documentation/revenuecat.md +0 -305
  94. package/documentation/storage-and-credentials.md +0 -201
  95. package/documentation/stripe.md +0 -262
  96. package/documentation/upgrading.md +0 -754
  97. package/documentation/vercel.md +0 -241
@@ -1,471 +0,0 @@
1
- # Cloudflare prebuilt connection
2
-
3
- Import `cloudflare()` independently from
4
- `@zackbart/connecta/providers/cloudflare`. The deployment chooses one of two
5
- interfaces. The default is a deliberate, hand-written surface over
6
- Cloudflare's v4 REST API. Its fifty-one tools combine ergonomic, fully
7
- described operations for common work with three guarded escape hatches for the
8
- rest of Cloudflare's fast-moving control plane. The other choice is
9
- Cloudflare's official whole-API hosted MCP, which exposes `search` and
10
- `execute` with live provider-owned schemas. Both are ordinary connectors and
11
- neither is reachable from Connecta's root entry.
12
-
13
- ```ts
14
- import { cloudflare } from "@zackbart/connecta/providers/cloudflare";
15
-
16
- const edge = cloudflare("cloudflare_prod", {
17
- surface: "api", // optional; this is the backward-compatible default
18
- title: "Production edge",
19
- purpose: "DNS and cache administration for the production estate",
20
- zoneId: "0a1b2c3d4e5f60718293a4b5c6d7e8f9",
21
- accountId: "9f8e7d6c5b4a30291817263544332211",
22
- instructions: "Never purge the whole zone during business hours.",
23
- });
24
- ```
25
-
26
- Use Cloudflare's hosted code-mode interface instead:
27
-
28
- ```ts
29
- const wholeApi = cloudflare("cloudflare_mcp", {
30
- surface: "mcp",
31
- purpose: "Cloudflare administration outside the curated REST workflows",
32
- });
33
- ```
34
-
35
- Use the legacy user-scoped Global API Key when an existing deployment needs it:
36
-
37
- ```ts
38
- const legacyEdge = cloudflare("cloudflare_legacy", {
39
- purpose: "Legacy account administration",
40
- authentication: "globalApiKey",
41
- });
42
- ```
43
-
44
- The operator UI then asks for the Cloudflare user email and Global API Key as
45
- separate fields. The default remains a scoped API token.
46
-
47
- The `id` owns the ordinary connector namespaces; use a different id for every
48
- Cloudflare account or estate. `purpose` is required because an agent choosing
49
- between a production and a staging instance needs to know which one answers the
50
- question. Account `instructions` are appended to the maintained guide and
51
- cannot change the connector's safety classification.
52
-
53
- ## Choosing an interface
54
-
55
- Use the API interface when its projected named tools cover the work. Connecta
56
- owns those schemas, projections, typed errors, pagination, and the split
57
- between read-only and mutating escape hatches.
58
-
59
- Use `surface: "mcp"` when broad product coverage matters more than projected
60
- results. Cloudflare's official server covers more than 2,500 API endpoints
61
- through two code-mode tools. `search` reads the OpenAPI document and is
62
- read-only. `execute` can run a program containing any authorized HTTP method,
63
- so Connecta always routes it through approval. A program that happens to use
64
- only GET cannot be proven observational from the tool schema.
65
-
66
- The MCP catalog and schemas come from the live server. The release manifest
67
- classifies the two known names but does not replace their schemas. OAuth is the
68
- default. A headless deployment may instead pass `auth` with a scoped API token.
69
- The credential remains the provider-side permission boundary either way. The
70
- MCP interface accepts `callAdmission` when the deployment has a concurrency or
71
- call-rate requirement; it does not reuse the API interface's REST-wide budget.
72
-
73
- The remaining sections document the hand-written API interface. MCP tool
74
- arguments and results are intentionally read from the live server instead.
75
-
76
- ## No SDK, on purpose
77
-
78
- Cloudflare publishes an official `cloudflare` npm SDK, and this connection does
79
- not use it. The SDK's value is typed request wrappers and pagination helpers.
80
- Both are things this connection replaces rather than consumes: an agent needs a
81
- projected result and a `page.hasMore` boolean, not Cloudflare's full response
82
- object, so the SDK's types would be re-projected away at the boundary. What the
83
- dependency would cost is real — an optional peer with its own install step and
84
- version skew, an import that never belongs in the root graph, and a second
85
- opinion about what a Cloudflare call looks like.
86
-
87
- The API itself does not need one. It is authenticated `fetch` with a uniform
88
- `{ success, errors, messages, result, result_info }` response envelope. JSON,
89
- raw bytes, and multipart request bodies all use Web APIs, which keeps the
90
- provider Workers-clean and means `@zackbart/connecta/providers/cloudflare`
91
- installs and runs with nothing extra. `test/package-surface.test.ts` pins the
92
- claim: the `cloudflare` package must not appear in `dependencies`,
93
- `peerDependencies`, or `devDependencies`, and every import in the provider
94
- must be relative.
95
-
96
- ## API credentials
97
-
98
- The default credential is a scoped Cloudflare API token, sent as
99
- `Authorization: Bearer <token>`. Create it under My Profile → API Tokens →
100
- Create Token.
101
-
102
- Set `authentication: "globalApiKey"` to use the legacy user-scoped scheme. The
103
- credential form stores two encrypted fields and sends them as `X-Auth-Email`
104
- and `X-Auth-Key`. The Global API Key has the same access as its Cloudflare user
105
- across every account and zone that user can reach. It cannot be scoped, only
106
- one can exist per user, and it has no expiry or IP limits. Cloudflare recommends
107
- API tokens for new use, but existing Global API Keys remain supported.
108
-
109
- Grant only what the deployment needs:
110
-
111
- | Tools | Token permission | Scope |
112
- | --- | --- | --- |
113
- | Zone discovery and settings reads | Zone Read | Zone |
114
- | `update_zone_setting` | Zone Settings Write | Zone |
115
- | Zone ruleset reads | Relevant Rules product Read permission, such as Transform Rules Read or Firewall Services Read | Zone |
116
- | `list_dns_records`, `get_dns_record` | DNS Read | Zone |
117
- | `create_dns_record`, `update_dns_record`, `delete_dns_record` | DNS Write | Zone |
118
- | `purge_cache` | Cache Purge | Zone |
119
- | Worker script/deployment reads | Workers Scripts Read | Account |
120
- | Worker writes through named or raw tools | Workers Scripts Write | Account |
121
- | KV reads | Workers KV Storage Read | Account |
122
- | KV creates, renames, writes, and deletes | Workers KV Storage Write | Account |
123
- | R2 reads | Workers R2 Storage Read | Account |
124
- | R2 creates, changes, uploads, deletes, and configuration writes | Workers R2 Storage Write | Account |
125
- | Pages reads | Cloudflare Pages Read | Account |
126
- | Pages retries, rollbacks, domains, purges, uploads, and deletes | Cloudflare Pages Write | Account |
127
- | Images, Stream, Email Routing, D1, Queues, and other raw calls | Matching product Read or Write permission | Account or Zone |
128
-
129
- Most names above appear directly in the token editor. Ruleset access is split
130
- by product and phase, so grant the narrow Rules permission for the phases the
131
- agent must inspect rather than looking for one generic "Zone Rulesets Read"
132
- scope. "Cache Purge" is a single permission with no Read/Write split, and
133
- Cloudflare's own reference renders a few labels differently between its
134
- Dashboard and API tabs.
135
-
136
- `verify_api_token` needs no permission beyond the token existing. In legacy
137
- mode, `verify_global_api_key` reads `/user` to confirm the email and key pair.
138
- The connection UI Test action at `/` runs the matching check before storage.
139
-
140
- Cloudflare rate-limits *authentication failures* aggressively and separately
141
- from the global limit: a few requests with a bad token return HTTP 429 with
142
- code `10502`, "Too many authentication failures". That surfaces here as
143
- `rate_limited`, not `auth_required`, which is correct — the token may well be
144
- fine by the time the window clears — but it means a broken token should be
145
- diagnosed once with `verify_api_token` rather than by retrying real calls.
146
-
147
- ## Scoping
148
-
149
- `zoneId` and `accountId` are deployment defaults, not restrictions. When a
150
- default is set, the corresponding argument drops out of the tool's `required`
151
- list and calls that omit it use the default; a call may still pass a different
152
- id. When no default is set, the argument is required and the schema's own
153
- description names the discovery tool that produces it — `list_zones` for a
154
- zone, `list_accounts` for an account.
155
-
156
- That is the discovery flow worth knowing: Cloudflare addresses almost
157
- everything by an opaque 32-character id, and an agent that only knows a domain
158
- name must call `list_zones` with `name: "example.com"` first. Configuring
159
- `zoneId` removes that hop entirely for a single-zone deployment.
160
-
161
- `list_zones` is the one tool a configured `accountId` deliberately does *not*
162
- reach. It is the discovery step, and a default that quietly filtered it would
163
- be a restriction in all but name — one with no argument that escapes it, since
164
- an empty `accountId` would fall back to the default again. A deployment that
165
- wants zones from one account passes `accountId` explicitly, and the property
166
- says so.
167
-
168
- ## API tools
169
-
170
- The named surface covers workflows that benefit most from concise schemas and
171
- projections:
172
-
173
- | Area | Reads | Writes |
174
- | --- | --- | --- |
175
- | Zones | discovery, details, one setting at a time, rulesets | update a setting |
176
- | DNS/cache | list and get records | create, update, delete, targeted/full purge |
177
- | Workers | scripts, settings, deployments | delete a script |
178
- | KV | namespaces, keys, bulk values | create/rename/delete namespace, bulk write/delete |
179
- | R2 | buckets, object metadata, CORS | create/update/delete bucket, delete object |
180
- | Pages | projects, deployments, domains | retry/rollback/delete deployments, add/delete domains, purge build cache, delete project |
181
-
182
- Every named tool carries a complete hand-written input schema: closed
183
- (`additionalProperties: false`), with an accurate `required` list, an `enum` on
184
- every constrained field, endpoint-specific pagination bounds, and a description
185
- on every property. `test/cloudflare-provider.test.ts` walks the surface and
186
- asserts those properties rather than leaving them as a claim.
187
-
188
- The per-setting zone tools accept Cloudflare's current non-empty setting ids,
189
- including `webmcp_enabled` and `webmcp_packs`. Cloudflare's
190
- [rendered setting reference](https://developers.cloudflare.com/api/resources/zones/subresources/settings/)
191
- and current OpenAPI now agree on both beta ids. Results preserve the value and
192
- the actual `editable` flag Cloudflare returned; Connecta does not apply the
193
- document's defaults locally.
194
-
195
- KV namespace jurisdiction is creation-only. `create_kv_namespace` accepts
196
- `eu`, `fedramp`, or `us`, and namespace reads preserve the returned value.
197
- R2 uses a different enum: `default`, `eu`, `us`, or `fedramp`, sent through
198
- `cf-r2-jurisdiction` by all eight named R2 operations.
199
-
200
- ### What the named surface deliberately leaves out
201
-
202
- A named tool is a permanent line item in every deployment's catalog, so the
203
- surface was measured against the escape hatches rather than assumed to beat
204
- them ([#350](https://github.com/zackbart/connecta/issues/350), evidence in
205
- [`eval/current-version/results/issue-350-evidence.md`](https://github.com/zackbart/connecta/blob/main/eval/current-version/results/issue-350-evidence.md)).
206
- Two named tools lost that comparison, a third followed one of them off the
207
- surface to keep a policy pair on one route, and a fourth left because
208
- Cloudflare deprecated the only endpoint it could call:
209
-
210
- - **R2 CORS writes.** `set_r2_cors` declared its rule list as free-form objects
211
- — the untyped body this connection refuses everywhere else — so its schema
212
- validated the bucket name and waved through the part of the call that
213
- actually fails. It also returned Cloudflare's response unprojected and was
214
- destructive either way, which left nothing for it to beat
215
- `cloudflare_api_mutate` on. `delete_r2_cors` did not lose that comparison:
216
- it returned a fixed `{deleted: true}` behind a closed output schema and
217
- refused every malformed argument locally. It went anyway, because naming only
218
- the delete would mean one CORS policy is set through the raw route and
219
- cleared through a named tool. Read a policy with `get_r2_cors`; change one
220
- with `cloudflare_api_mutate` at
221
- `PUT`/`DELETE /accounts/{accountId}/r2/buckets/{bucketName}/cors`. This is the
222
- same split the [DNS record types](#dns-record-types) already use: structured
223
- bodies stay readable through named tools and writable through the
224
- approval-gated raw route.
225
- - **R2 account metrics.** `get_r2_metrics` took one account id, put it in a
226
- path, and returned the response untouched — `cloudflare_api_get` at
227
- `/accounts/{accountId}/r2/metrics` does the same thing without a permanent
228
- catalog line.
229
- - **The bulk zone-settings read.** `list_zone_settings` called
230
- `GET /zones/{zoneId}/settings`, which Cloudflare's published document now
231
- marks `deprecated: true` along with its `PATCH` sibling
232
- ([#361](https://github.com/zackbart/connecta/issues/361)). There is no bulk
233
- replacement to repoint it at: the supported operations are the per-setting
234
- `GET` and `PATCH /zones/{zoneId}/settings/{settingId}` that `get_zone_setting`
235
- and `update_zone_setting` already call. Keeping the tool would have meant
236
- maintaining a name over a contract Cloudflare has announced it intends to
237
- stop honouring — and #350 had already measured this one as the sharpest
238
- passthrough on the surface: it took a zone id, projected nothing, and *grew*
239
- the payload 22.7% by wrapping Cloudflare's settings array in a page object an
240
- unpaginated endpoint never filled. Read one setting with `get_zone_setting`.
241
- Where the whole set is genuinely wanted — an audit, a config diff — an
242
- operator can still ask for it explicitly through `cloudflare_api_get` at
243
- `/zones/{zoneId}/settings`, which is the honest place for a call whose
244
- endpoint is on its way out: named by the caller, at the caller's risk, rather
245
- than promised by connecta's catalog.
246
-
247
- Because `scripts/drift/cloudflare-endpoints.json` records the endpoints this
248
- connection *calls*, dropping the tool drops the row — so
249
- `npm run drift:check -- --specs` is quiet about zone settings by construction
250
- rather than by a recorded exception. A path reached only through a hatch is
251
- named by the caller, so it was never a touched endpoint.
252
-
253
- The surviving 48 named tools all refuse malformed arguments locally, which is
254
- the one thing no escape hatch can do: a hatch's path is an opaque string, so it
255
- can only check that a path is a path.
256
-
257
- ### The whole-v4 escape hatch
258
-
259
- Cloudflare adds products and endpoints faster than a curated connector should
260
- grow tool names. Three provider-relative tools cover the rest without turning
261
- method classification into user input:
262
-
263
- - `cloudflare_api_get` accepts only GET and is explicitly read-only. JSON is the
264
- default; `responseType: "text" | "base64"` retrieves scripts, logs, R2
265
- objects, and media bodies without pretending they have a JSON envelope.
266
- - `cloudflare_api_mutate` accepts JSON POST, PUT, PATCH, and DELETE. It is always
267
- destructive, even when a particular POST is merely additive. It preserves
268
- ordinary non-envelope JSON from endpoints such as `/graphql`.
269
- - `cloudflare_api_upload` accepts POST or PUT plus exactly one of raw text,
270
- base64 bytes, or multipart fields/files. It is always destructive and reads
271
- no local files.
272
-
273
- All three accept explicit endpoint-specific headers, which supports R2
274
- jurisdictions, conditional requests, encryption controls, and object metadata.
275
- `Authorization`, `Cookie`, `Host`, `Content-Length`, `Content-Type`, and
276
- `Transfer-Encoding` remain connector-owned and are refused: authentication,
277
- host selection, content type, and request framing are not the caller's to set.
278
- That refused list lives in the usage guide rather than in each header property
279
- description, because the compact renderer inlines a property description once
280
- per tool that shares it, and repeating it three times pushed
281
- `cloudflare_api_upload`'s compact input shape past the 1,024-byte discovery
282
- budget.
283
-
284
- Paths are relative to `/client/v4`. Absolute URLs, protocol-relative paths,
285
- `..` traversal, fragments, and embedded query strings are refused locally;
286
- query parameters are explicit name/value pairs. These tools reuse the same
287
- credential, admission budget, abort signal, envelope parsing, and typed failure
288
- mapping as named tools. They do not widen the configured credential's access.
289
-
290
- This is intentionally not OpenAPI ingestion: it creates three stable tools,
291
- not one tool per Cloudflare operation. For example, an agent can list Images at
292
- `/accounts/{accountId}/images/v1`, manage Stream at
293
- `/accounts/{accountId}/stream`, manage Email Routing at
294
- `/zones/{zoneId}/email/routing/rules`, reach D1 at
295
- `/accounts/{accountId}/d1/database`, and reach Queues at
296
- `/accounts/{accountId}/queues`. The endpoint-specific query and body shape still
297
- comes from Cloudflare's API reference.
298
-
299
- ### Where the `perPage` bounds come from
300
-
301
- `api()` enforces every hand-written schema, so an out-of-range `perPage` is
302
- refused locally before it reaches Cloudflare. That is only a favor when the bound is really
303
- Cloudflare's, so the schemas record which ones are and the descriptions say so
304
- out loud:
305
-
306
- | Tool | `perPage` | Default | Whose bound |
307
- | --- | --- | --- | --- |
308
- | `list_accounts`, `list_zones` | 5–50 | 20 | Cloudflare's, as documented |
309
- | `list_kv_namespaces` | 1–1000 | 20 | Cloudflare's, as documented |
310
- | `list_dns_records` | 1–1000 | 100 | Cloudflare's minimum; the ceiling is ours |
311
- | `list_pages_projects` | 1–100 | — | Ours entirely |
312
-
313
- Two need the note. Cloudflare's schema documents `per_page` on
314
- `/zones/{id}/dns_records` as 1 to **5,000,000** — a nominal ceiling no listing
315
- will honor — so this connection caps it at 1,000, the same conservative-reading
316
- move as the [one-variant purge rule](#cache-purging): a local cap an agent is
317
- told about beats a page size that fails somewhere inside Cloudflare. And
318
- `/accounts/{id}/pages/projects` documents no bounds and no default at all, so
319
- 1 to 100 is a choice made here and labeled as one.
320
-
321
- ### DNS record types
322
-
323
- Cloudflare accepts 21 record types, exported as `CLOUDFLARE_DNS_RECORD_TYPES`.
324
- Eight of them take a single `content` string; the other thirteen (CAA, CERT,
325
- DNSKEY, DS, HTTPS, LOC, NAPTR, SMIMEA, SRV, SSHFP, SVCB, TLSA, URI) take a
326
- per-type structured `data` object with its own field set.
327
-
328
- `list_dns_records` filters on all 21. `create_dns_record` and
329
- `update_dns_record` accept only the eight content-based types, exported as
330
- `CLOUDFLARE_CONTENT_DNS_RECORD_TYPES`. Supporting the rest would mean either a
331
- free-form `data` passthrough — the untyped `{}` this connection exists to
332
- avoid — or thirteen more hand-written schemas for record types that are rare in
333
- day-to-day zone administration. Structured-data records stay fully readable.
334
- The named create/update tools omit them and the enum says so rather than letting
335
- the call reach Cloudflare and 400; an operator who needs one can use the
336
- approval-gated raw mutation tool with Cloudflare's documented per-type `data`
337
- body.
338
-
339
- ### Cache purging
340
-
341
- `purge_cache` takes exactly one variant per call: `everything: true`, or one of
342
- `files`, `tags`, `hosts`, or `prefixes`. Cloudflare caps a purge at 100
343
- operations per request (500 files on Enterprise), and all four targeted methods
344
- are available on every plan — tag, host, and prefix purging is no longer
345
- Enterprise-only.
346
-
347
- The one-variant rule is this connection's contract, not a documented API
348
- restriction. Cloudflare's schema models the body as `anyOf`, which does not
349
- forbid combining, and the only explicit exclusivity statement in its
350
- documentation is about the Workers cache binding rather than the REST endpoint.
351
- Refusing a combined call locally is the conservative reading: an agent gets a
352
- clear `invalid_args` naming the conflict instead of a purge whose actual scope
353
- is ambiguous. If a future deployment needs combined tag-and-prefix purging,
354
- that is a deliberate change to make here, not something to discover in
355
- production.
356
-
357
- ## Results
358
-
359
- Reads return Cloudflare's `result` unwrapped and projected: identity and
360
- description fields kept, plan/permission/meta noise dropped, `snake_case`
361
- renamed to `camelCase`. A zone comes back as `id`, `name`, `status`, `paused`,
362
- `type`, `accountId`, `accountName`, `plan`, `nameServers`, and timestamps —
363
- not the forty-field object Cloudflare sends.
364
-
365
- Every named tool declares useful top-level output keys. Zone settings, Worker
366
- settings and deployments, KV namespaces and bulk operations, rulesets, R2 CORS,
367
- and Pages resources use maintained result schemas instead of an open
368
- "Cloudflare object" declaration. The three raw escape hatches remain open by
369
- design because their result fields depend on the caller-supplied endpoint.
370
-
371
- Paginated lists add a `page` object derived from `result_info`:
372
- `{ page, perPage, count, totalCount, totalPages, hasMore }`. `hasMore` is the
373
- field to branch on.
374
-
375
- Some endpoints do not work that way, and the schemas say so rather than leaving
376
- an agent to discover it. `list_zone_rulesets`, `list_r2_buckets`,
377
- `list_r2_objects`, and `list_kv_keys` paginate by cursor and return
378
- `nextCursor` instead of `page`. On those four the `cursor` argument says the
379
- endpoint pages by cursor rather than page number, and the `nextCursor` result
380
- says it is the only signal and that no `page` object is coming — so the loop
381
- condition is legible from either end of one tool, without fetching the guide.
382
- `list_worker_scripts` reports no counters at all and omits `page` entirely.
383
-
384
- Projected resource reads expose `raw: true` where the provider's larger object
385
- is commonly useful. `cloudflare_api_get` is the universal unprojected escape
386
- hatch. Raw shapes can hit a deployment's result cap, so programs should still
387
- filter and project before returning them.
388
-
389
- ## Typed failures
390
-
391
- Cloudflare's error envelope carries an array of `{ code, message }` entries and
392
- sometimes a nested `error_chain`; the connection flattens the whole chain into
393
- the failure message so the provider's own code number survives to the agent.
394
-
395
- | Cloudflare | Connecta failure | Agent behavior |
396
- | --- | --- | --- |
397
- | 429 | `rate_limited`, retryable | Waits `retryAfterMs` — the `retry-after` header when present, otherwise the full five-minute window |
398
- | 401 or 403 | `auth_required`, not retryable | Stops and reports which permission is missing |
399
- | 400 with a credential-shaped code (1001, 6003, 6111, 9103, 9106, 9107) | `auth_required`, not retryable | Stops; the header or key is malformed, not the arguments |
400
- | 400, 409, 422 | `invalid_args`, not retryable | Repairs the arguments |
401
- | 404 | `not_found`, not retryable | Re-runs discovery for the id |
402
- | 5xx or a transport error | `unavailable`, retryable | Retries |
403
-
404
- The 404 row is the unambiguous half of the `not_found` rule
405
- ([H11](./provider-conventions.md#h11--errors-are-mapped-to-what-the-caller-does-next)).
406
- Cloudflare refuses a token that may not touch a resource with 401 or 403, so a
407
- 404 is an absence rather than a permission gap wearing a miss, and an agent can
408
- act on it: confirm the zone or account id with `list_zones` or `list_accounts`
409
- and address the right one. Notion's 404, which proves neither, stays
410
- `connector_call_failed`.
411
-
412
- The six credential-shaped codes deserve a caveat: Cloudflare publishes no
413
- official table mapping error codes to causes, so that set is assembled from
414
- community reports and probing, not from documentation. The same goes for the
415
- claim below that `10000` is overloaded — that is an observation about responses
416
- seen in practice. Treat both as well-supported readings that Cloudflare could
417
- invalidate without notice. Prefer `verify_api_token` or
418
- `verify_global_api_key`, as configured, when a diagnosis matters.
419
-
420
- Two ordering decisions are deliberate. The 429 branch is checked before the
421
- authentication codes, because Cloudflare reuses the generic `10000` code on
422
- throttled responses and reading a rate limit as an auth failure would tell an
423
- agent to stop when it should wait. And `10000` is *not* itself treated as an
424
- auth code: Cloudflare returns it for "Authentication error" but also for
425
- ordinary validation failures like "Invalid pagination cursor" and
426
- "domain_name is required", so routing on it would tell an agent its credential was
427
- broken when its arguments were. Genuine `10000` auth failures arrive with 401
428
- or 403 and are caught by status.
429
-
430
- Because the connection declares an operator-managed credential rather than an
431
- OAuth flow, an `auth_required` failure resolves to the `operator_config`
432
- recovery mode — the fix is a human updating the credential, not an authorization
433
- URL the agent can open. A missing credential fails that way before any request
434
- is made.
435
-
436
- Some failures never reach Cloudflare at all. A blank scope id, a `purge_cache`
437
- call with no variant or two, and an `update_dns_record` with nothing to change
438
- are all refused locally as `invalid_args` with a validation issue attached,
439
- because a round trip that can only 400 is a wasted call and a worse
440
- explanation.
441
-
442
- ## Rate limits
443
-
444
- Cloudflare documents a global limit of
445
- [1,200 requests per five minutes per user](https://developers.cloudflare.com/fundamentals/api/reference/limits/),
446
- counted cumulatively across the dashboard, API keys, and API tokens. The
447
- connection declares a matching rolling-window admission budget plus a
448
- `maxConcurrency` of 6, overridable with the `maxConcurrency` option.
449
-
450
- The budget is a best-effort approximation of the per-user limit, not an
451
- enforcement of it. Each runtime keeps its own counter, so N Worker isolates or
452
- Node processes serving one deployment can each admit up to 1,200 — and the
453
- dashboard traffic of a human sharing the account is counted by Cloudflare but
454
- not by Connecta. `maxConcurrency` is the bound that actually protects a shared
455
- credential, because a single `execute_code` program can fan out far faster than
456
- the window notices.
457
-
458
- ## Contract checks
459
-
460
- `npm run providers:check` compares the 49 fixed REST endpoints with
461
- Cloudflare's published OpenAPI document and the two MCP names, endpoint, and
462
- OAuth support with Cloudflare's official MCP page. It needs no Cloudflare
463
- credential. The MCP schemas are not vendored or reconstructed: the live
464
- `tools/list` response remains the contract agents receive.
465
-
466
- ## Conventions
467
-
468
- This connection is audited against
469
- [the provider conventions](./provider-conventions.md). Its verdict per
470
- convention, including every recorded exception, is the Cloudflare section of
471
- [the provider audit](https://github.com/zackbart/connecta/blob/main/records/provider-audit.md).
@@ -1,176 +0,0 @@
1
- # Connector guides
2
-
3
- Descriptions and schemas say *what* a connector's tools are. They do not say
4
- which tool to prefer, which id format an address quirk expects, how the service
5
- paginates, or how hard you may hammer it. A connector's optional `usageGuide`
6
- is where that goes — deployment-owned configuration, served by the `skills`
7
- meta-tool as `connector:<id>` and returned verbatim, with a bounded `summary`
8
- surfaced in discovery so an agent learns the guide exists at the moment it
9
- matters. See [meta-tools](./meta-tools.md#connector-guide-selection) for the
10
- discovery contract and [connectors](./connectors.md#catalog-contract) for how a
11
- guide is configured.
12
-
13
- ## The shape of a guide
14
-
15
- `usageGuide` accepts a bare markdown string, which is still the shortest
16
- correct configuration, or the structured form:
17
-
18
- ```ts
19
- const notion = remoteMcp("notion", {
20
- url: "https://mcp.notion.com/mcp",
21
- description: "Notion — pages, databases, comments",
22
- auth: { type: "oauth" },
23
- usageGuide: {
24
- content: `# Notion usage
25
-
26
- Search before listing: \`notion.search\` covers pages and databases in one call.
27
-
28
- - Page ids are dashed UUIDs. Strip the trailing slug from a pasted URL first.
29
- - Paginate with \`start_cursor\`; \`page_size\` is capped at 100.
30
- - Writes replace blocks wholesale — read the block, merge, then write.
31
- `,
32
- summary: "Search before listing; dashed-UUID page ids; cursor pagination",
33
- },
34
- });
35
- ```
36
-
37
- The field is on the `Connector` interface, not on a factory, so it works
38
- identically for `api()`, `remoteMcp()`, a prebuilt connection, and a
39
- hand-written connector. It is deployment-owned configuration like everything
40
- else here: an edit and a redeploy, never a runtime registration.
41
-
42
- `content` is returned byte for byte by `skills({ name: "connector:<id>" })`.
43
- `summary` is normalized and must fit 120 characters. A longer configured value
44
- refuses construction instead of silently changing the operator's words. Omit
45
- it and connecta derives the same bounded summary the skills listing uses: the
46
- first meaningful body paragraph, joined across Markdown's physical line wraps,
47
- with frontmatter, fences, rules, comments, and tables skipped. When the
48
- paragraph does not fit, connecta keeps a useful complete sentence when one
49
- fits, then prefers a clause or word boundary before adding an ellipsis. A heading is used
50
- only when the guide has no body, and the connector's description is the last
51
- resort. A derived summary is usually worse than a written one — it was written
52
- to open a document, not to answer "is this guide relevant to what I am about
53
- to do".
54
-
55
- `connector:<id>` is the only address for a guide, and built-in skill names are
56
- bare identifiers, so a guide can never shadow or be shadowed by `usage`: a
57
- connector whose id is literally `usage` is listed as `connector:usage`, and
58
- `skills({ name: "usage" })` still returns the built-in guide. Every miss —
59
- unknown name, unknown connector, connector with no guide — is an explicit
60
- error. Nothing silently falls back to the generic guide, because a generic
61
- answer to a specific question is worse than no answer.
62
-
63
- Discovery text is conditional on the deployment actually having a guide. Short
64
- pointers in the `skills`, `search_tools`, `call_destructive_tool`, and
65
- `execute_code` descriptions appear only when at least one visible connector
66
- declares one. The detailed selection rules live only in the built-in `usage`
67
- skill. That skill stays byte-identical across deployments, including its
68
- per-connector-guides section, so an agent reads it at most once per task.
69
-
70
- ## What belongs in a guide
71
-
72
- The test is not "is this true" but "can a schema carry it". If a schema can,
73
- fix the schema instead — a constraint in the schema is enforced, is visible at
74
- the moment of the call, and costs nothing to a caller who never fetches the
75
- guide.
76
-
77
- So a guide carries:
78
-
79
- - **Tool preference.** Which of two plausible tools is the right one, and the
80
- fact that decides it.
81
- - **Sequence.** What must happen first — resolving a name to an id, listing a
82
- parent before a child.
83
- - **Identity and address quirks.** The id format an address expects, the
84
- difference between the id in the URL and the id the API wants.
85
- - **Pagination conventions.** The cursor field, the page cap, whether the
86
- connector has more than one convention.
87
- - **Units and aliases.** What a bare number means; what the service calls the
88
- thing the caller calls something else.
89
- - **Reduction advice.** Which fields matter, for a downstream whose results are
90
- large and whose schemas you do not control.
91
- - **Rate-limit etiquette**, with the number.
92
-
93
- And a guide does not carry: anything readable off the schema, background on
94
- what the service is, marketing, a tool list, or a second copy of the tool
95
- descriptions. It is fetched into a live context window — every line that
96
- repeats the schema is a line that displaced one that did not.
97
-
98
- **Style.** Write for the agent, not the operator; the built-in `usage` skill
99
- (`src/skills.ts`) is the model. Concise and imperative, leading with the
100
- decision rather than the background ("Search before listing", not "Notion has a
101
- search API"). Prefer short bullets to prose. Name exact tool addresses and
102
- argument names. State a constraint with its number.
103
-
104
- ## The `required` rule
105
-
106
- `required: true` on the structured form means: fetch this guide before every
107
- operation on this connector. It surfaces as `guideRequired: true` with
108
- `guideRequiredReasons: ["connector_required"]` on discovery results.
109
-
110
- It is an instruction, not a gate. Nothing refuses the call — connecta tells the
111
- agent to read the guide first and then believes it, because a server-side
112
- refusal here would be a policy engine, which
113
- [`ethos.md`](../ethos.md) refuses.
114
-
115
- Reserve it for connectors whose correct arguments or sequence *cannot* be
116
- expressed by the downstream tool schema at all: generic API wrappers whose one
117
- broad tool name carries no endpoint vocabulary, and cross-operation conventions
118
- no single schema can state. Two categories already produce the flag on their
119
- own and must not be hand-declared for it — an unannotated or write-capable tool
120
- (`approval_required`) and a compact schema that was capped
121
- (`schema_truncated`). `connector_required` and `approval_required` survive
122
- exact schema expansion; `schema_truncated` clears once describe returns the
123
- exact shape.
124
-
125
- The failure mode of over-declaring is quiet and expensive: an agent that must
126
- fetch a guide before every call pays that fetch on the calls where the schema
127
- was already complete and unambiguous. `required` earns its cost on connectors
128
- where the alternative is a wrong call, not on connectors where it is a slightly
129
- slower right one.
130
-
131
- ## Provider conventions
132
-
133
- The maintained prebuilt connections come in two shapes, and each has its own
134
- convention set — including the shape of its usage guide, which is one of the
135
- few things both shapes fully own:
136
-
137
- - [Hand-written HTTP providers](./provider-conventions.md#hand-written-http-providers)
138
- (H1–H14) — `api()` surfaces where Connecta owns every name, schema,
139
- projection, and error. Cloudflare, Notion, and Vercel.
140
- - [Hosted-MCP proxies](./provider-conventions.md#hosted-mcp-proxies) (P1–P13) —
141
- `remoteMcp()` wrappers where the downstream owns the catalog and Connecta
142
- owns the endpoint, credential, classification, guide, and budget. Linear,
143
- Stripe, Mixpanel, and RevenueCat.
144
-
145
- Both sets are judged by one measure: what the convention saves the model that
146
- interacts with connecta, priced in discovery tokens, wrong-tool selection,
147
- argument retries, or result size. The same document defines
148
- [what a provider audit checks](./provider-conventions.md#what-the-audit-checks),
149
- so [#342](https://github.com/zackbart/connecta/issues/342) can run against it
150
- convention by convention rather than by taste.
151
-
152
- Two of those conventions decide how a guide is written, and they differ by
153
- shape. A hand-written provider's guide carries only what a schema cannot,
154
- because it owns the schemas and should fix them instead
155
- ([H13](./provider-conventions.md#h13--the-guide-carries-only-what-a-schema-cannot)).
156
- A proxy's guide carries the reduction and identity-resolution advice its
157
- schemas will never carry, because it cannot change them
158
- ([P7](./provider-conventions.md#p7--the-guide-carries-the-reduction-advice-the-schemas-cannot),
159
- [P8](./provider-conventions.md#p8--identity-resolution-comes-before-action)).
160
-
161
- ## Tests that enforce this
162
-
163
- `test/meta-tools.test.ts` owns the guide behavior end to end: the skills
164
- listing carrying one entry per guided connector, summaries joining a
165
- hard-wrapped opening paragraph and shortening at readable boundaries,
166
- configured summaries refusing construction past the bound, heading and
167
- description fallbacks, markup skipping, whitespace-only guides treated as no
168
- guide, content returned verbatim including surrounding padding, identical
169
- content in two deployments staying isolated, every miss erroring rather than
170
- falling back to the generic guide with an identically labelled skills list on
171
- each branch, the `guide` pointer in search output, and `guideRequired`
172
- appearing for connector-required conventions, approval-bound tools, and
173
- truncated schemas — and being absent from a search that asked for no schemas.
174
- `test/server.test.ts` owns the conditional half: it compares a guide-free
175
- deployment's four short pointers against a guided one's, and asserts the
176
- complete `usage` skill is byte-identical between them.