@zackbart/connecta 0.24.3 → 0.25.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 (145) hide show
  1. package/AGENTS.md +58 -30
  2. package/CHANGELOG.md +317 -1
  3. package/README.md +5 -6
  4. package/dist/advertised-schema.js +53 -0
  5. package/dist/auth/downstream-oauth.js +490 -106
  6. package/dist/branding.d.ts +31 -2
  7. package/dist/branding.js +116 -8
  8. package/dist/call-admission.js +139 -113
  9. package/dist/catalog-service.js +141 -105
  10. package/dist/catalog.js +258 -33
  11. package/dist/concurrency.js +24 -23
  12. package/dist/connector-scope.js +6 -37
  13. package/dist/connectors/api.d.ts +3 -3
  14. package/dist/connectors/api.js +10 -2
  15. package/dist/connectors/guarded-fetch.js +34 -4
  16. package/dist/connectors/remote-mcp.d.ts +6 -5
  17. package/dist/connectors/remote-mcp.js +325 -245
  18. package/dist/credential-contract.d.ts +9 -0
  19. package/dist/credential-rules.js +2 -2
  20. package/dist/credentials.d.ts +6 -1
  21. package/dist/credentials.js +45 -3
  22. package/dist/errors.d.ts +11 -3
  23. package/dist/errors.js +2 -1
  24. package/dist/execute.js +363 -257
  25. package/dist/executor-admission.js +67 -50
  26. package/dist/executors/quickjs-runtime.js +4 -2
  27. package/dist/executors/quickjs.js +251 -210
  28. package/dist/fix-prompt.js +37 -0
  29. package/dist/index.d.ts +11 -2
  30. package/dist/index.js +63 -21
  31. package/dist/invocation.js +324 -285
  32. package/dist/meta-tools.js +304 -114
  33. package/dist/oauth-callback-outcome.js +94 -0
  34. package/dist/oauth-sealing.js +29 -0
  35. package/dist/operator-ui/fix-prompts.js +87 -0
  36. package/dist/operator-ui/generated.js +2 -2
  37. package/dist/operator-ui/model.d.ts +46 -1
  38. package/dist/operator-ui/setup-commands.js +53 -0
  39. package/dist/operator-ui/view.js +109 -2
  40. package/dist/program-source.js +205 -0
  41. package/dist/providers/cloudflare.d.ts +14 -2
  42. package/dist/providers/cloudflare.js +107 -16
  43. package/dist/providers/linear.d.ts +26 -4
  44. package/dist/providers/linear.js +19 -4
  45. package/dist/providers/mixpanel.d.ts +16 -3
  46. package/dist/providers/mixpanel.js +13 -2
  47. package/dist/providers/notion.d.ts +8 -1
  48. package/dist/providers/notion.js +85 -12
  49. package/dist/providers/revenuecat.d.ts +30 -4
  50. package/dist/providers/revenuecat.js +70 -5
  51. package/dist/providers/stripe.d.ts +10 -3
  52. package/dist/providers/stripe.js +30 -4
  53. package/dist/providers/vercel.js +12 -2
  54. package/dist/registry.d.ts +13 -5
  55. package/dist/registry.js +317 -258
  56. package/dist/routes/activity.js +120 -137
  57. package/dist/routes/credentials.js +174 -163
  58. package/dist/routes/mcp.js +129 -70
  59. package/dist/routes/oauth-management.js +60 -71
  60. package/dist/routes/oauth.js +44 -16
  61. package/dist/routes/operator.js +82 -0
  62. package/dist/routes/shared.d.ts +2 -0
  63. package/dist/routes/ui-data.js +210 -0
  64. package/dist/routes/ui.js +64 -32
  65. package/dist/runtime/admission.js +43 -0
  66. package/dist/runtime/call-admission.js +67 -0
  67. package/dist/runtime/connector-scope.js +72 -0
  68. package/dist/runtime/run.js +174 -0
  69. package/dist/runtime/services.js +130 -0
  70. package/dist/runtime/storage.js +66 -0
  71. package/dist/server.js +111 -89
  72. package/dist/skills.js +1 -1
  73. package/dist/storage/file.js +39 -2
  74. package/dist/storage/memory.js +28 -16
  75. package/dist/timeout.js +17 -47
  76. package/dist/types.d.ts +64 -7
  77. package/dist/ui.d.ts +16 -2
  78. package/dist/ui.js +68 -176
  79. package/dist/url-safety.js +157 -0
  80. package/dist/version.d.ts +1 -1
  81. package/dist/version.js +1 -1
  82. package/documentation/architecture.md +368 -182
  83. package/documentation/auth.md +277 -176
  84. package/documentation/code-mode.md +479 -329
  85. package/documentation/meta-tools.md +494 -417
  86. package/ethos.md +44 -47
  87. package/examples/worker/AGENTS.md +2 -1
  88. package/examples/worker/README.md +62 -14
  89. package/examples/worker/src/d1-storage.ts +116 -0
  90. package/examples/worker/src/index.ts +18 -21
  91. package/examples/worker/wrangler.jsonc +16 -4
  92. package/package.json +11 -4
  93. package/templates/node/.env.example +3 -3
  94. package/templates/node/AGENTS.md +5 -4
  95. package/templates/node/README.md +2 -1
  96. package/templates/node/package.json +1 -1
  97. package/templates/node/src/index.ts +23 -22
  98. package/dist/auth/downstream-oauth.d.ts +0 -200
  99. package/dist/catalog-fingerprint.d.ts +0 -19
  100. package/dist/catalog-limits.d.ts +0 -9
  101. package/dist/catalog-service.d.ts +0 -263
  102. package/dist/catalog.d.ts +0 -87
  103. package/dist/concurrency.d.ts +0 -3
  104. package/dist/connectors/guarded-fetch.d.ts +0 -92
  105. package/dist/credential-rules.d.ts +0 -85
  106. package/dist/execute.d.ts +0 -154
  107. package/dist/executor-result.d.ts +0 -10
  108. package/dist/executors/quickjs-child.d.ts +0 -1
  109. package/dist/executors/quickjs-protocol.d.ts +0 -67
  110. package/dist/favicon.d.ts +0 -1
  111. package/dist/identity.d.ts +0 -4
  112. package/dist/invocation.d.ts +0 -74
  113. package/dist/mcp-result.d.ts +0 -8
  114. package/dist/meta-tools.d.ts +0 -129
  115. package/dist/operator-ui/generated.d.ts +0 -2
  116. package/dist/operator-ui/view.d.ts +0 -151
  117. package/dist/result-shapes.d.ts +0 -13
  118. package/dist/retired-toolkits.d.ts +0 -2
  119. package/dist/routes/activity.d.ts +0 -2
  120. package/dist/routes/credentials.d.ts +0 -2
  121. package/dist/routes/mcp.d.ts +0 -10
  122. package/dist/routes/oauth-management.d.ts +0 -2
  123. package/dist/routes/oauth.d.ts +0 -2
  124. package/dist/routes/ui.d.ts +0 -2
  125. package/dist/server.d.ts +0 -9
  126. package/dist/skills.d.ts +0 -44
  127. package/dist/timeout.d.ts +0 -21
  128. package/dist/tool-safety.d.ts +0 -9
  129. package/documentation/call-admission.md +0 -158
  130. package/documentation/cloudflare.md +0 -471
  131. package/documentation/connector-guides.md +0 -176
  132. package/documentation/connectors.md +0 -431
  133. package/documentation/linear.md +0 -193
  134. package/documentation/mixpanel.md +0 -160
  135. package/documentation/notion.md +0 -308
  136. package/documentation/operations.md +0 -359
  137. package/documentation/operator-ui.md +0 -135
  138. package/documentation/optional-modules-upgrade.md +0 -243
  139. package/documentation/provider-conventions.md +0 -729
  140. package/documentation/request-admission.md +0 -204
  141. package/documentation/revenuecat.md +0 -305
  142. package/documentation/storage-and-credentials.md +0 -254
  143. package/documentation/stripe.md +0 -262
  144. package/documentation/upgrading.md +0 -768
  145. package/documentation/vercel.md +0 -241
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Client setup snippets for one MCP endpoint: the deployment's `/mcp` or a
3
+ * `/mcp/<pool>`. Three formats and no more — Claude Code's CLI, Codex's CLI,
4
+ * and the `mcpServers` JSON most other clients read — because a longer list
5
+ * is a list to keep current.
6
+ *
7
+ * None of them carries a credential. A client authenticates through the
8
+ * deployment's inbound auth — OAuth discovery where that is configured — and
9
+ * a token pasted into a snippet on this page would be one rendered by it.
10
+ */
11
+ /**
12
+ * The name a client files this server under. Derived from the deployment's
13
+ * configured server name, reduced to the characters every client CLI accepts
14
+ * unquoted, with the pool appended so two endpoints never collide.
15
+ */
16
+ export function clientServerName(serverName, pool) {
17
+ const base = (serverName ?? "")
18
+ .toLowerCase()
19
+ .replace(/[^a-z0-9_-]+/g, "-")
20
+ .replace(/-{2,}/g, "-")
21
+ .replace(/^-+|-+$/g, "")
22
+ .slice(0, 48) || "connecta";
23
+ return pool ? `${base}-${pool}` : base;
24
+ }
25
+ /** The URL for a pool: `/mcp` plus its name, which config limits to `[a-z0-9_-]`. */
26
+ export function poolEndpointUrl(mcpUrl, pool) {
27
+ return `${mcpUrl.replace(/\/+$/, "")}/${encodeURIComponent(pool)}`;
28
+ }
29
+ /** POSIX single-quoting, only when the value needs it. */
30
+ function shellWord(value) {
31
+ return /^[A-Za-z0-9_./:@%+=,~-]+$/.test(value)
32
+ ? value
33
+ : `'${value.replace(/'/g, `'"'"'`)}'`;
34
+ }
35
+ export function clientSetupCommands(name, url) {
36
+ return [
37
+ {
38
+ id: "claude",
39
+ label: "Claude Code",
40
+ text: `claude mcp add --transport http ${shellWord(name)} ${shellWord(url)}`,
41
+ },
42
+ {
43
+ id: "codex",
44
+ label: "Codex",
45
+ text: `codex mcp add ${shellWord(name)} --url ${shellWord(url)}`,
46
+ },
47
+ {
48
+ id: "json",
49
+ label: "JSON config",
50
+ text: JSON.stringify({ mcpServers: { [name]: { type: "http", url } } }, null, 2),
51
+ },
52
+ ];
53
+ }
@@ -13,8 +13,8 @@ export function pageForPath(path) {
13
13
  export function info(message) {
14
14
  return { message, tone: "info" };
15
15
  }
16
- export function failure(message) {
17
- return { message, tone: "error" };
16
+ export function failure(message, fix) {
17
+ return fix ? { message, tone: "error", fix } : { message, tone: "error" };
18
18
  }
19
19
  export function initialState(page) {
20
20
  return {
@@ -97,6 +97,113 @@ export function connectorStatusLabel(status) {
97
97
  export function toolCountLabel(count) {
98
98
  return `${count} ${count === 1 ? "tool" : "tools"}`;
99
99
  }
100
+ export function connectorStatusTone(status) {
101
+ if (status === "ok")
102
+ return "ok";
103
+ if (status === "auth_required")
104
+ return "warn";
105
+ if (status === "loading")
106
+ return "neutral";
107
+ return "danger";
108
+ }
109
+ export function summarizeConnectors(connectors) {
110
+ const summary = {
111
+ total: connectors.length,
112
+ connected: 0,
113
+ attention: 0,
114
+ unavailable: 0,
115
+ tools: 0,
116
+ drifting: 0,
117
+ };
118
+ for (const connector of connectors) {
119
+ if (connector.status === "ok")
120
+ summary.connected += 1;
121
+ else if (connector.status === "auth_required")
122
+ summary.attention += 1;
123
+ else if (connector.status !== "loading")
124
+ summary.unavailable += 1;
125
+ summary.tools += connector.toolCount || 0;
126
+ if (driftState(connector.catalogDrift) === "warning")
127
+ summary.drifting += 1;
128
+ }
129
+ return summary;
130
+ }
131
+ /**
132
+ * The one line above the list. Connected and tool counts are always present;
133
+ * the two counts an operator may have to act on appear only when they are not
134
+ * zero, so a healthy deployment stays short.
135
+ */
136
+ export function connectorSummaryParts(summary) {
137
+ return [
138
+ { text: `${summary.connected} connected`, tone: "neutral" },
139
+ ...(summary.attention
140
+ ? [
141
+ {
142
+ text: `${summary.attention} need${summary.attention === 1 ? "s" : ""} authorization`,
143
+ tone: "warn",
144
+ },
145
+ ]
146
+ : []),
147
+ ...(summary.unavailable
148
+ ? [{ text: `${summary.unavailable} unavailable`, tone: "danger" }]
149
+ : []),
150
+ { text: toolCountLabel(summary.tools), tone: "neutral" },
151
+ ];
152
+ }
153
+ /**
154
+ * The badge for each call path, keyed by the server's classification so the
155
+ * planned third state is a row here rather than a new branch in a component.
156
+ */
157
+ export const TOOL_SAFETY_BADGE = {
158
+ runs_in_programs: {
159
+ label: "runs in programs",
160
+ tone: "ok",
161
+ title: "Explicitly read-only: execute_code programs may call it without asking.",
162
+ },
163
+ needs_approval: {
164
+ label: "needs approval",
165
+ tone: "warn",
166
+ title: "Not explicitly read-only: calls cross call_destructive_tool, where the host asks first.",
167
+ },
168
+ };
169
+ /**
170
+ * What the page says about a connector that is not usable, one fixed sentence
171
+ * per problem kind the server classified. This is the only account of a
172
+ * failure the Connections page renders: a connector's status message can quote
173
+ * a downstream error body, and a downstream error body can quote the secret
174
+ * that was just rejected, so the raw text stays in the deployment's log and
175
+ * never reaches the payload or the DOM. The fix prompt beside it is keyed off
176
+ * the same kind.
177
+ */
178
+ const PROBLEM_COPY = {
179
+ connector_unavailable: "Unavailable: its status check or catalog load failed, or did not finish in time. The deployment's log has the downstream error.",
180
+ oauth_required: "Needs OAuth authorization: no grant is stored, or the stored grant expired or was revoked.",
181
+ credential_required: "Needs a credential: nothing usable is stored in its credential slot.",
182
+ auth_required: "Needs authorization. Its secret lives in deployment configuration, not on this page.",
183
+ credential_mismatch: "The stored credential does not match the fields this connector declares, so it cannot be used.",
184
+ catalog_failed: "Connected, but its tool catalog could not be loaded, so none of its tools are served.",
185
+ };
186
+ export function problemCopy(problem) {
187
+ return problem ? PROBLEM_COPY[problem] ?? null : null;
188
+ }
189
+ /** Who owns this connector's downstream credentials, in two words. */
190
+ export function authScopeLabel(scope) {
191
+ return scope === "personal" ? "personal auth" : "shared auth";
192
+ }
193
+ /**
194
+ * What this identity may do with a connector, in one line. Being able to use it
195
+ * is implied by seeing it at all, so the sentence covers authentication, which
196
+ * is the part that differs between operators.
197
+ */
198
+ export function permissionLabel(connector) {
199
+ if (connector.permissions?.manageSharedAuth) {
200
+ return "You can manage shared authentication for this connection.";
201
+ }
202
+ if (connector.permissions?.connectPersonal) {
203
+ return "You can connect your own account to this connection.";
204
+ }
205
+ return "Authentication for this connection is managed by your deployment.";
206
+ }
100
207
  /**
101
208
  * The four categories, in the order an operator should read them: what the
102
209
  * deployment refuses to call, what it can no longer call, what contradicts a
@@ -0,0 +1,205 @@
1
+ // Program source handling shared by every executor. Web-API only: execute_code
2
+ // runs it before handing a program to QuickJS or a Dynamic Worker, and the
3
+ // QuickJS normalizer runs it too for callers that drive that executor directly.
4
+ /** A whole-program markdown fence, as QuickJS's normalizer recognizes one. */
5
+ const FENCE = /^```[\w-]*\s*\n([\s\S]*?)\n?```$/;
6
+ /** Leading whitespace and comments, which may precede the arrow expression. */
7
+ const LEADING_TRIVIA = /^(?:\s+|\/\/[^\n]*|\/\*[\s\S]*?\*\/)+/;
8
+ /** One character of an identifier, keyword, or number; `\\` for escapes. */
9
+ const WORD = /[\w$\\\u0080-\uffff]/;
10
+ /**
11
+ * Words after which a `/` begins a regular expression rather than dividing.
12
+ * Any other word is an identifier or a literal, which a `/` divides.
13
+ */
14
+ const REGEX_AFTER_WORD = new Set([
15
+ "await", "case", "delete", "do", "else", "in", "instanceof", "new", "of",
16
+ "return", "throw", "typeof", "void", "yield",
17
+ ]);
18
+ /**
19
+ * Remove the statement terminators after a program's arrow expression:
20
+ * `async () => { … };`, with any whitespace or comments around the trailing
21
+ * semicolons, becomes `async () => { … }` with those comments kept. Both
22
+ * executors evaluate a program as a parenthesized expression, where a
23
+ * terminator is a syntax error on QuickJS and, on a Dynamic Worker, either the
24
+ * same error or a program that silently returns `undefined`. Models often end
25
+ * a program with `};`, so P1 accepts it on both.
26
+ *
27
+ * Deliberately narrow, so it cannot broaden what a program is: only a program
28
+ * whose first token is `async` or `(` — what QuickJS treats as a function
29
+ * expression — and only when every semicolon at bracket depth zero trails it,
30
+ * with nothing but whitespace and comments after. `async () => 1; 2();` keeps
31
+ * both its semicolons, and a bare statement body is never touched, since its
32
+ * trailing `;` can be an empty loop body. A whole-program markdown fence is
33
+ * looked through, and dropped when a terminator came out from under it.
34
+ *
35
+ * A small lexer, not a parser: it skips strings, template literals (with
36
+ * `${}` nesting), comments, and regular expressions, telling a regex from a
37
+ * division by the token before it. Any source it cannot close — an
38
+ * unterminated string or comment, unbalanced brackets — comes back unchanged,
39
+ * so the executor reports the same error it always did.
40
+ */
41
+ export function withoutProgramTerminator(code) {
42
+ const fenced = FENCE.exec(code.trim())?.[1];
43
+ const source = fenced === undefined ? code : fenced.trim();
44
+ const stripped = stripTerminators(source);
45
+ return stripped === source ? code : stripped;
46
+ }
47
+ function stripTerminators(source) {
48
+ const head = source.replace(LEADING_TRIVIA, "");
49
+ if (!/^async\b/.test(head) && !head.startsWith("("))
50
+ return source;
51
+ const n = source.length;
52
+ let depth = 0;
53
+ /** Bracket depth at which each open template substitution resumes its template. */
54
+ const substitutions = [];
55
+ /** Positions of the top-level semicolons, all of which must trail. */
56
+ const terminators = [];
57
+ /**
58
+ * The previous significant token: a punctuator character or a word, with a
59
+ * string, template, or regex literal recorded as `0` — a value, like a number.
60
+ */
61
+ let previous = "";
62
+ let i = 0;
63
+ /** Scan template text from `from` to its closing backtick or next `${`. */
64
+ const template = (from) => {
65
+ for (let j = from; j < n; j++) {
66
+ const c = source[j];
67
+ if (c === "\\") {
68
+ j++;
69
+ }
70
+ else if (c === "`") {
71
+ return j + 1;
72
+ }
73
+ else if (c === "$" && source[j + 1] === "{") {
74
+ substitutions.push(depth);
75
+ depth++;
76
+ return j + 2;
77
+ }
78
+ }
79
+ return undefined;
80
+ };
81
+ while (i < n) {
82
+ const c = source[i];
83
+ if (/\s/.test(c)) {
84
+ i++;
85
+ continue;
86
+ }
87
+ if (c === "/" && source[i + 1] === "/") {
88
+ const end = source.indexOf("\n", i);
89
+ i = end === -1 ? n : end;
90
+ continue;
91
+ }
92
+ if (c === "/" && source[i + 1] === "*") {
93
+ const end = source.indexOf("*/", i + 2);
94
+ if (end === -1)
95
+ return source;
96
+ i = end + 2;
97
+ continue;
98
+ }
99
+ if (c === ";") {
100
+ if (depth === 0)
101
+ terminators.push(i);
102
+ previous = ";";
103
+ i++;
104
+ continue;
105
+ }
106
+ // Code after a top-level semicolon means the program is more than one
107
+ // expression; that is for the executor to refuse, not for us to repair.
108
+ if (terminators.length > 0)
109
+ return source;
110
+ if (c === '"' || c === "'") {
111
+ let j = i + 1;
112
+ while (j < n && source[j] !== c) {
113
+ if (source[j] === "\n")
114
+ return source;
115
+ j += source[j] === "\\" ? 2 : 1;
116
+ }
117
+ if (j >= n)
118
+ return source;
119
+ i = j + 1;
120
+ previous = "0";
121
+ continue;
122
+ }
123
+ if (c === "`") {
124
+ const end = template(i + 1);
125
+ if (end === undefined)
126
+ return source;
127
+ i = end;
128
+ previous = source[end - 1] === "`" ? "0" : "{";
129
+ continue;
130
+ }
131
+ if (c === "}" && substitutions.at(-1) === depth - 1) {
132
+ substitutions.pop();
133
+ depth--;
134
+ const end = template(i + 1);
135
+ if (end === undefined)
136
+ return source;
137
+ i = end;
138
+ previous = source[end - 1] === "`" ? "0" : "{";
139
+ continue;
140
+ }
141
+ if (c === "/" && startsRegex(previous)) {
142
+ let j = i + 1;
143
+ let inClass = false;
144
+ for (; j < n; j++) {
145
+ const r = source[j];
146
+ if (r === "\n")
147
+ return source;
148
+ if (r === "\\")
149
+ j++;
150
+ else if (r === "[")
151
+ inClass = true;
152
+ else if (r === "]")
153
+ inClass = false;
154
+ else if (r === "/" && !inClass)
155
+ break;
156
+ }
157
+ if (j >= n)
158
+ return source;
159
+ i = j + 1;
160
+ while (i < n && /[\w$]/.test(source[i]))
161
+ i++;
162
+ previous = "0";
163
+ continue;
164
+ }
165
+ if (WORD.test(c)) {
166
+ let j = i + 1;
167
+ while (j < n && WORD.test(source[j]))
168
+ j++;
169
+ previous = source.slice(i, j);
170
+ i = j;
171
+ continue;
172
+ }
173
+ if (c === "(" || c === "[" || c === "{")
174
+ depth++;
175
+ else if (c === ")" || c === "]" || c === "}") {
176
+ if (depth === 0)
177
+ return source;
178
+ depth--;
179
+ }
180
+ previous = c;
181
+ i++;
182
+ }
183
+ if (depth !== 0 || substitutions.length > 0 || terminators.length === 0) {
184
+ return source;
185
+ }
186
+ let out = "";
187
+ let from = 0;
188
+ for (const at of terminators) {
189
+ out += source.slice(from, at);
190
+ from = at + 1;
191
+ }
192
+ return out + source.slice(from);
193
+ }
194
+ /** Whether a `/` after `previous` opens a regular expression. */
195
+ function startsRegex(previous) {
196
+ if (previous === "")
197
+ return true;
198
+ if (WORD.test(previous[0])) {
199
+ // A number or an identifier divides; a keyword introduces an operand.
200
+ return REGEX_AFTER_WORD.has(previous);
201
+ }
202
+ // After a closing bracket a value precedes; after any other punctuator
203
+ // an operand is expected.
204
+ return previous !== ")" && previous !== "]" && previous !== "}";
205
+ }
@@ -6,9 +6,21 @@ export declare const CLOUDFLARE_API_BASE = "https://api.cloudflare.com/client/v4
6
6
  export declare const CLOUDFLARE_MCP_ENDPOINT = "https://mcp.cloudflare.com/mcp";
7
7
  /** Authentication schemes accepted by Cloudflare's v4 API. */
8
8
  export type CloudflareAuthentication = "apiToken" | "globalApiKey";
9
- /** See documentation/cloudflare.md#dns-record-types. */
9
+ /**
10
+ * The 21 record types Cloudflare accepts. Eight carry a single `content`
11
+ * string; the other thirteen carry a per-type structured `data` object.
12
+ * `list_dns_records` filters on all 21 because reading them costs nothing.
13
+ */
10
14
  export declare const CLOUDFLARE_DNS_RECORD_TYPES: readonly ["A", "AAAA", "CAA", "CERT", "CNAME", "DNSKEY", "DS", "HTTPS", "LOC", "MX", "NAPTR", "NS", "OPENPGPKEY", "PTR", "SMIMEA", "SRV", "SSHFP", "SVCB", "TLSA", "TXT", "URI"];
11
- /** Content-valued types only; see documentation/cloudflare.md#dns-record-types. */
15
+ /**
16
+ * Content-valued types only, and the only types `create_dns_record` and
17
+ * `update_dns_record` accept. Covering the thirteen structured-`data` types
18
+ * would mean either a free-form `data` passthrough — the untyped body this
19
+ * connection exists to avoid — or thirteen more hand-written schemas for record
20
+ * types that are rare in day-to-day zone administration. They stay fully
21
+ * readable; writing one goes through the approval-gated raw mutation tool with
22
+ * Cloudflare's documented per-type `data` body.
23
+ */
12
24
  export declare const CLOUDFLARE_CONTENT_DNS_RECORD_TYPES: readonly ["A", "AAAA", "CNAME", "MX", "NS", "OPENPGPKEY", "PTR", "TXT"];
13
25
  interface CloudflareCommonOptions {
14
26
  /** Human-readable display name; defaults identify the selected interface. */
@@ -1,4 +1,14 @@
1
- /** See documentation/cloudflare.md#no-sdk-on-purpose. */
1
+ /**
2
+ * No official `cloudflare` SDK on purpose. What the SDK sells — typed request
3
+ * wrappers and pagination helpers — is what this connection replaces: an agent
4
+ * gets a projected result and a `page.hasMore` boolean, so the SDK's types
5
+ * would be re-projected away at the boundary, and the dependency would cost an
6
+ * optional peer, an install step, and an import that never belongs in the root
7
+ * graph. Cloudflare's v4 API is authenticated `fetch` over a uniform
8
+ * `{ success, errors, messages, result, result_info }` envelope, so Web APIs
9
+ * alone keep this provider Workers-clean. `test/package-surface.test.ts` pins
10
+ * it: no `cloudflare` package in any dependency field, every import relative.
11
+ */
2
12
  import { api, defined } from "../connectors/api.js";
3
13
  import { remoteMcp, withCredentialDefaults, } from "../connectors/remote-mcp.js";
4
14
  import { vettedCatalog, withVettedCatalog } from "../catalog-drift.js";
@@ -8,7 +18,11 @@ import { ConnectorCallError } from "../errors.js";
8
18
  export const CLOUDFLARE_API_BASE = "https://api.cloudflare.com/client/v4";
9
19
  /** Cloudflare's official whole-API hosted MCP endpoint. */
10
20
  export const CLOUDFLARE_MCP_ENDPOINT = "https://mcp.cloudflare.com/mcp";
11
- /** See documentation/cloudflare.md#dns-record-types. */
21
+ /**
22
+ * The 21 record types Cloudflare accepts. Eight carry a single `content`
23
+ * string; the other thirteen carry a per-type structured `data` object.
24
+ * `list_dns_records` filters on all 21 because reading them costs nothing.
25
+ */
12
26
  export const CLOUDFLARE_DNS_RECORD_TYPES = [
13
27
  "A",
14
28
  "AAAA",
@@ -32,7 +46,15 @@ export const CLOUDFLARE_DNS_RECORD_TYPES = [
32
46
  "TXT",
33
47
  "URI",
34
48
  ];
35
- /** Content-valued types only; see documentation/cloudflare.md#dns-record-types. */
49
+ /**
50
+ * Content-valued types only, and the only types `create_dns_record` and
51
+ * `update_dns_record` accept. Covering the thirteen structured-`data` types
52
+ * would mean either a free-form `data` passthrough — the untyped body this
53
+ * connection exists to avoid — or thirteen more hand-written schemas for record
54
+ * types that are rare in day-to-day zone administration. They stay fully
55
+ * readable; writing one goes through the approval-gated raw mutation tool with
56
+ * Cloudflare's documented per-type `data` body.
57
+ */
36
58
  export const CLOUDFLARE_CONTENT_DNS_RECORD_TYPES = [
37
59
  "A",
38
60
  "AAAA",
@@ -43,7 +65,17 @@ export const CLOUDFLARE_CONTENT_DNS_RECORD_TYPES = [
43
65
  "PTR",
44
66
  "TXT",
45
67
  ];
46
- /** See documentation/cloudflare.md#rate-limits. */
68
+ /**
69
+ * Cloudflare documents a global limit of 1,200 requests per five minutes per
70
+ * user, counted cumulatively across the dashboard, API keys, and API tokens
71
+ * (developers.cloudflare.com/fundamentals/api/reference/limits/). The matching
72
+ * rolling window here is a best-effort approximation, not an enforcement: each
73
+ * runtime keeps its own counter, so N isolates or processes can each admit
74
+ * 1,200, and a human's dashboard traffic counts for Cloudflare but not for us.
75
+ * `maxConcurrency` is the bound that actually protects a shared credential,
76
+ * because one `execute_code` program can fan out faster than the window
77
+ * notices.
78
+ */
47
79
  function admissionPolicy(maxConcurrency) {
48
80
  return {
49
81
  rules: [
@@ -155,7 +187,17 @@ function errorCodes(errors) {
155
187
  * would risk telling an agent its token was broken when its arguments were.
156
188
  * Genuine 10000 auth failures arrive with 401 or 403 and are caught by status.
157
189
  *
158
- * See documentation/cloudflare.md#typed-failures for provenance and routing.
190
+ * Provenance worth knowing before editing this set: Cloudflare publishes no
191
+ * official table mapping error codes to causes, so these six — and the 10000
192
+ * observation above — come from community reports and probing rather than
193
+ * documentation. They are well-supported readings Cloudflare could invalidate
194
+ * without notice; prefer `verify_api_token` or `verify_global_api_key` when a
195
+ * diagnosis actually matters.
196
+ *
197
+ * Cloudflare also rate-limits authentication failures separately from the
198
+ * global limit: a few requests with a bad token return 429 with code 10502.
199
+ * That is one more reason to diagnose a broken token once with a verify tool
200
+ * rather than by retrying real calls.
159
201
  */
160
202
  const AUTH_ERROR_CODES = new Set([1001, 6003, 6111, 9103, 9106, 9107]);
161
203
  /**
@@ -169,7 +211,9 @@ const AUTH_ERROR_CODES = new Set([1001, 6003, 6111, 9103, 9106, 9107]);
169
211
  function failureFor(status, headers, errors) {
170
212
  const detail = describeErrors(errors);
171
213
  const codes = errorCodes(errors);
172
- // Ordering rationale: documentation/cloudflare.md#typed-failures.
214
+ // 429 is checked before the authentication codes on purpose: Cloudflare
215
+ // reuses the generic 10000 code on throttled responses, and reading a rate
216
+ // limit as an auth failure would tell an agent to stop when it should wait.
173
217
  if (status === 429) {
174
218
  const wait = retryAfterMs(headers);
175
219
  return new ConnectorCallError("rate_limited", `Cloudflare rate limit reached (HTTP 429). ${detail} The documented limit is 1,200 requests per five minutes per user, counted across the dashboard and every token.`,
@@ -184,7 +228,10 @@ function failureFor(status, headers, errors) {
184
228
  if (status === 400 || status === 409 || status === 422) {
185
229
  return new ConnectorCallError("invalid_args", `Cloudflare rejected the request (HTTP ${status}). ${detail}`);
186
230
  }
187
- // 404 rationale: documentation/cloudflare.md#typed-failures.
231
+ // Cloudflare refuses a token that may not touch a resource with 401 or 403,
232
+ // so a 404 here is a real absence rather than a permission gap wearing a
233
+ // miss — the unambiguous case `not_found` exists for, and something an agent
234
+ // can act on by re-running discovery for the id.
188
235
  if (status === 404) {
189
236
  return new ConnectorCallError("not_found", `Cloudflare found no such resource (HTTP 404). ${detail} Confirm the zone or account id with list_zones or list_accounts.`);
190
237
  }
@@ -576,7 +623,17 @@ const RAW_INPUT_PROPERTY = {
576
623
  type: "boolean",
577
624
  description: "Return Cloudflare's unprojected result instead of the lean shape. Use only when a field the projection drops is genuinely needed; the raw shape is much larger.",
578
625
  };
579
- /** Bound provenance: documentation/cloudflare.md#where-the-perpage-bounds-come-from. */
626
+ /**
627
+ * Local enforcement of a `perPage` range is only a favor when the bound is
628
+ * really Cloudflare's, so `bounds` decides what the description admits to.
629
+ * `list_accounts`/`list_zones` (5–50) and `list_kv_namespaces` (1–1000) are
630
+ * Cloudflare's documented bounds. `list_dns_records` takes Cloudflare's
631
+ * minimum but is `clamped` at 1,000: Cloudflare's schema documents `per_page`
632
+ * on `/zones/{id}/dns_records` up to 5,000,000, a nominal ceiling no listing
633
+ * will honor, and a local cap an agent is told about beats a page size that
634
+ * fails somewhere inside Cloudflare. `list_pages_projects` is `undocumented` —
635
+ * Cloudflare publishes no bounds and no default for it, so 1–100 is ours.
636
+ */
580
637
  function pagingInputProperties(minPerPage, maxPerPage, options = {}) {
581
638
  const { defaultPerPage, bounds = "cloudflare" } = options;
582
639
  const defaultNote = defaultPerPage === undefined
@@ -601,7 +658,12 @@ function pagingInputProperties(minPerPage, maxPerPage, options = {}) {
601
658
  },
602
659
  };
603
660
  }
604
- /** Cursor convention: documentation/cloudflare.md#results. */
661
+ /**
662
+ * Four endpoints — `list_zone_rulesets`, `list_r2_buckets`, `list_r2_objects`,
663
+ * `list_kv_keys` — page by cursor and get no `page` object. Both the argument
664
+ * and the result say so, so the loop condition is legible from either end of
665
+ * one tool rather than only to a reader who compared all of them.
666
+ */
605
667
  const CURSOR_INPUT_PROPERTY = {
606
668
  type: "string",
607
669
  description: "Opaque cursor from a previous call's nextCursor. This endpoint pages by cursor, not page number.",
@@ -1013,7 +1075,12 @@ const QUERY_INPUT_PROPERTY = {
1013
1075
  additionalProperties: false,
1014
1076
  },
1015
1077
  };
1016
- // Header boundary: documentation/cloudflare.md#the-whole-v4-escape-hatch.
1078
+ // Connector-owned and refused: Authorization, Cookie, Host, Content-Length,
1079
+ // Content-Type, Transfer-Encoding — authentication, host selection, content
1080
+ // type, and request framing are not the caller's to set. That list stays out of
1081
+ // this description on purpose: the compact renderer inlines a shared property
1082
+ // description once per tool, and spelling the six out three times pushed
1083
+ // cloudflare_api_upload's compact input past the 1,024-byte discovery budget.
1017
1084
  const HEADERS_INPUT_PROPERTY = {
1018
1085
  type: "array",
1019
1086
  description: "Endpoint headers as name/value pairs, e.g. cf-r2-jurisdiction or Range. Connector-owned headers are refused.",
@@ -1412,7 +1479,11 @@ function buildTools(scope, authentication) {
1412
1479
  path: "/zones",
1413
1480
  query: {
1414
1481
  name: optionalString(args, "name"),
1415
- // Undefaulted on purpose — see documentation/cloudflare.md#scoping.
1482
+ // Undefaulted on purpose: list_zones is the discovery step,
1483
+ // and quietly filtering it by a configured accountId would
1484
+ // be a restriction in all but name — one with no argument
1485
+ // that escapes it, since an empty accountId would fall back
1486
+ // to the default again.
1416
1487
  "account.id": optionalString(args, "accountId"),
1417
1488
  status: optionalString(args, "status"),
1418
1489
  page: optionalNumber(args, "page"),
@@ -1424,7 +1495,13 @@ function buildTools(scope, authentication) {
1424
1495
  cfTool("get_zone", "Fetch one zone's settings summary by id: status, plan, name servers, and owning account.", readOnly, "zoneId", scope.zoneId, {
1425
1496
  raw: RAW_INPUT_PROPERTY
1426
1497
  }, [], ZONE_SCHEMA, getResult(send, (args) => ({ method: "GET", path: `/zones/${encodeURIComponent(zoneArg(args))}` }), (result, args) => args["raw"] === true ? result : projectZone(result))),
1427
- // Removed tools: documentation/cloudflare.md#what-the-named-surface-deliberately-leaves-out.
1498
+ // No bulk `list_zone_settings` on purpose (#361): Cloudflare's published
1499
+ // document marks GET /zones/{zoneId}/settings and its PATCH sibling
1500
+ // deprecated with no bulk replacement, and the tool projected nothing while
1501
+ // *growing* the payload 22.7% by wrapping an unpaginated settings array in a
1502
+ // page object. Read one setting here; an operator who genuinely wants the
1503
+ // whole set names /zones/{zoneId}/settings through cloudflare_api_get, at
1504
+ // the caller's risk rather than promised by connecta's catalog.
1428
1505
  cfTool("get_zone_setting", "Get one zone setting by its Cloudflare setting id, such as ssl, always_use_https, min_tls_version, brotli, or development_mode.", readOnly, "zoneId", scope.zoneId, {
1429
1506
  settingId: SETTING_ID_PROPERTY,
1430
1507
  raw: RAW_INPUT_PROPERTY
@@ -2019,8 +2096,18 @@ function buildTools(scope, authentication) {
2019
2096
  path: `/accounts/${encodeURIComponent(accountArg(args))}/r2/buckets/${encodeURIComponent(requireString(args, "bucketName"))}/objects/${encodeObjectKey(objectKey)}`,
2020
2097
  headers: r2Headers(args),
2021
2098
  }))),
2022
- // No `get_r2_metrics`, `set_r2_cors`, or `delete_r2_cors` on purpose (#350) —
2023
- // see documentation/cloudflare.md#what-the-named-surface-deliberately-leaves-out.
2099
+ // No `get_r2_metrics`, `set_r2_cors`, or `delete_r2_cors` on purpose (#350).
2100
+ // A named tool is a permanent line item in every deployment's catalog, and
2101
+ // these lost the comparison against the escape hatches: `get_r2_metrics`
2102
+ // took an account id, put it in a path, and returned the response untouched,
2103
+ // which `cloudflare_api_get` at /accounts/{accountId}/r2/metrics already
2104
+ // does. `set_r2_cors` declared its rule list as free-form objects — the
2105
+ // untyped body refused everywhere else — and returned Cloudflare's response
2106
+ // unprojected, so it beat the raw route on nothing. `delete_r2_cors`
2107
+ // validated fine and went anyway, because naming only the delete would mean
2108
+ // one CORS policy is set through the raw route and cleared through a named
2109
+ // tool. Read with `get_r2_cors`; write with `cloudflare_api_mutate` at
2110
+ // PUT/DELETE /accounts/{accountId}/r2/buckets/{bucketName}/cors.
2024
2111
  cfTool("get_r2_cors", "Get the browser CORS rules configured on an R2 bucket.", readOnly, "accountId", scope.accountId, {
2025
2112
  bucketName: R2_BUCKET_NAME_PROPERTY,
2026
2113
  jurisdiction: R2_JURISDICTION_PROPERTY
@@ -2342,8 +2429,12 @@ function buildTools(scope, authentication) {
2342
2429
  const zoneId = zoneArg(args);
2343
2430
  const targeted = ["files", "tags", "hosts", "prefixes"].filter((key) => Array.isArray(args[key]) && args[key].length > 0);
2344
2431
  const everything = args["everything"] === true;
2345
- // Cloudflare's purge body accepts exactly one variant. Refusing here
2346
- // turns a confusing provider 400 into a schema-shaped failure.
2432
+ // One variant per call is this connection's contract, not a
2433
+ // documented API restriction: Cloudflare's schema models the body as
2434
+ // `anyOf`, which does not forbid combining. Refusing locally gives an
2435
+ // agent `invalid_args` naming the conflict instead of a purge whose
2436
+ // actual scope is ambiguous. Combined purging would be a deliberate
2437
+ // change here.
2347
2438
  if (everything && targeted.length > 0) {
2348
2439
  throw new ConnectorCallError("invalid_args", "purge_cache takes either everything: true or one targeted list, never both.", {
2349
2440
  validation: {
@@ -19,18 +19,40 @@ export interface LinearOptions {
19
19
  authScope?: "shared" | "personal";
20
20
  /** Which workspace this is and what decisions it answers. */
21
21
  purpose: string;
22
- /** Required endpoint selection; see `documentation/linear.md`. */
22
+ /**
23
+ * Endpoint selection, required with no default. Linear's read-only endpoint
24
+ * advertises the `read` scope alone, so its token cannot reach the write APIs
25
+ * — a stronger guarantee than any annotation. Neither default is safe:
26
+ * read-write hands out access nobody asked for, and read-only breaks a
27
+ * writing deployment at runtime, where no agent can repair it.
28
+ */
23
29
  access: LinearAccess;
24
- /** OAuth or a personal API key; see `documentation/linear.md`. */
30
+ /**
31
+ * OAuth 2.1 with dynamic client registration by default. Linear's MCP server
32
+ * also accepts a personal API key in `Authorization: Bearer` — note this is
33
+ * the MCP endpoint's own documented contract, not the GraphQL API convention.
34
+ * A personal key carries the acting user's full permissions, so pair it with
35
+ * `access: "read-only"` unless the deployment genuinely writes.
36
+ */
25
37
  auth?: RemoteMcpAuth;
26
38
  /** Workspace-specific conventions appended to the maintained provider guide. */
27
39
  instructions?: string;
28
40
  /** Connector-specific inline result limit; omit to inherit the deployment. */
29
41
  maxResultBytes?: number;
30
- /** Optional per-runtime policy; see `documentation/linear.md#rate-limits`. */
42
+ /**
43
+ * Optional per-runtime policy. There is no default: Linear documents no
44
+ * MCP-specific limit, the GraphQL limits it rides are metered per user per
45
+ * hour (and its own page disagrees with itself on the API-key figure), and
46
+ * Connecta's counter is per runtime. Only the operator knows the workspace.
47
+ */
31
48
  callAdmission?: ConnectorCallAdmissionPolicy;
32
49
  }
33
- /** Release-reviewed manifest; see provider conventions P5 and P13. */
50
+ /**
51
+ * One release-reviewed manifest, used both to classify a live tool and as the
52
+ * baseline the drift check compares against, so the annotation a caller gets
53
+ * and the verdict a check reads can never disagree. Drift must surface as an
54
+ * unclassified tool on the approval path (P5), never a quiet re-guess.
55
+ */
34
56
  export declare const LINEAR_VETTED_CATALOG: import("../catalog-drift.js").VettedCatalog;
35
57
  /** A maintained Linear hosted-MCP connection. */
36
58
  export declare function linear(id: string, options: LinearOptions): Connector;