@zackbart/connecta 0.15.0 → 0.15.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +27 -0
- package/dist/providers/cloudflare.d.ts +5 -1
- package/dist/providers/cloudflare.d.ts.map +1 -1
- package/dist/providers/cloudflare.js +203 -70
- package/dist/providers/cloudflare.js.map +1 -1
- package/dist/providers/linear.d.ts.map +1 -1
- package/dist/providers/linear.js +4 -3
- package/dist/providers/linear.js.map +1 -1
- package/dist/providers/mixpanel.d.ts.map +1 -1
- package/dist/providers/mixpanel.js +4 -3
- package/dist/providers/mixpanel.js.map +1 -1
- package/dist/providers/stripe.d.ts.map +1 -1
- package/dist/providers/stripe.js +4 -3
- package/dist/providers/stripe.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/documentation/cloudflare.md +37 -19
- package/documentation/connectors.md +9 -7
- package/documentation/linear.md +6 -7
- package/documentation/mixpanel.md +6 -7
- package/documentation/stripe.md +12 -12
- package/ethos.md +1 -1
- package/package.json +1 -1
- package/src/providers/cloudflare.ts +275 -79
- package/src/providers/linear.ts +4 -3
- package/src/providers/mixpanel.ts +4 -3
- package/src/providers/stripe.ts +4 -3
- package/src/version.ts +1 -1
- package/templates/node/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,33 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this package are documented here.
|
|
4
4
|
|
|
5
|
+
## 0.15.1 — 2026-08-12
|
|
6
|
+
|
|
7
|
+
The Cloudflare connection now supports legacy user-scoped Global API Keys as
|
|
8
|
+
an explicit authentication mode. Scoped API tokens remain the default. The
|
|
9
|
+
three guarded raw tools still cover the full v4 path space without adding one
|
|
10
|
+
tool per endpoint, and ordinary JSON responses such as GraphQL results now
|
|
11
|
+
survive that path intact.
|
|
12
|
+
|
|
13
|
+
Nothing breaks for existing deployments. They keep their current API token,
|
|
14
|
+
credential form, verification tool, and 55-tool surface. A deployment that
|
|
15
|
+
needs the legacy scheme opts in and stores the Cloudflare user email and Global
|
|
16
|
+
API Key as separate encrypted fields.
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- **Legacy Cloudflare Global API Key authentication.** Set
|
|
21
|
+
`authentication: "globalApiKey"` to send operator-managed `X-Auth-Email` and
|
|
22
|
+
`X-Auth-Key` headers. `verify_global_api_key` checks the pair through
|
|
23
|
+
Cloudflare's authenticated user endpoint, and raw calls cannot replace either
|
|
24
|
+
connector-owned header.
|
|
25
|
+
|
|
26
|
+
### Fixed
|
|
27
|
+
|
|
28
|
+
- **Raw Cloudflare calls preserve non-envelope JSON.** Endpoints such as
|
|
29
|
+
`/graphql` return their complete JSON document instead of an undefined
|
|
30
|
+
`result`.
|
|
31
|
+
|
|
5
32
|
## 0.15.0 — 2026-08-10
|
|
6
33
|
|
|
7
34
|
Discovery now keeps strong action/object near-matches visible beside complete
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { Connector, ConnectorCredentialConfig } from "../types.js";
|
|
2
2
|
/** Cloudflare's v4 REST base. Override only for a proxy or a test double. */
|
|
3
3
|
export declare const CLOUDFLARE_API_BASE = "https://api.cloudflare.com/client/v4";
|
|
4
|
+
/** Authentication schemes accepted by Cloudflare's v4 API. */
|
|
5
|
+
export type CloudflareAuthentication = "apiToken" | "globalApiKey";
|
|
4
6
|
/**
|
|
5
7
|
* Every DNS record type the records API accepts, for filtering a list.
|
|
6
8
|
* Enumerated in the schema so an agent picks a legal type without reading
|
|
@@ -40,7 +42,9 @@ export interface CloudflareOptions {
|
|
|
40
42
|
zoneId?: string;
|
|
41
43
|
/** API base override for a proxy or a test double. Defaults to the v4 API. */
|
|
42
44
|
baseUrl?: string;
|
|
43
|
-
/**
|
|
45
|
+
/** Authentication scheme. Defaults to the recommended scoped API token. */
|
|
46
|
+
authentication?: CloudflareAuthentication;
|
|
47
|
+
/** Credential presentation override; credentials are always operator-managed. */
|
|
44
48
|
credential?: ConnectorCredentialConfig;
|
|
45
49
|
/** Account-specific conventions appended to the maintained provider guide. */
|
|
46
50
|
instructions?: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cloudflare.d.ts","sourceRoot":"","sources":["../../src/providers/cloudflare.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EACV,SAAS,EAGT,yBAAyB,EAE1B,MAAM,aAAa,CAAC;AAErB,6EAA6E;AAC7E,eAAO,MAAM,mBAAmB,yCAAyC,CAAC;AAE1E;;;;GAIG;AACH,eAAO,MAAM,2BAA2B,kLAsB9B,CAAC;AAEX;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,mCAAmC,yEAStC,CAAC;AAEX,MAAM,WAAW,iBAAiB;IAChC,6DAA6D;IAC7D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,sEAAsE;IACtE,OAAO,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,8EAA8E;IAC9E,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,
|
|
1
|
+
{"version":3,"file":"cloudflare.d.ts","sourceRoot":"","sources":["../../src/providers/cloudflare.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EACV,SAAS,EAGT,yBAAyB,EAE1B,MAAM,aAAa,CAAC;AAErB,6EAA6E;AAC7E,eAAO,MAAM,mBAAmB,yCAAyC,CAAC;AAE1E,8DAA8D;AAC9D,MAAM,MAAM,wBAAwB,GAAG,UAAU,GAAG,cAAc,CAAC;AAEnE;;;;GAIG;AACH,eAAO,MAAM,2BAA2B,kLAsB9B,CAAC;AAEX;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,mCAAmC,yEAStC,CAAC;AAEX,MAAM,WAAW,iBAAiB;IAChC,6DAA6D;IAC7D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,sEAAsE;IACtE,OAAO,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,8EAA8E;IAC9E,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,2EAA2E;IAC3E,cAAc,CAAC,EAAE,wBAAwB,CAAC;IAC1C,iFAAiF;IACjF,UAAU,CAAC,EAAE,yBAAyB,CAAC;IACvC,8EAA8E;IAC9E,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,8EAA8E;IAC9E,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,oDAAoD;IACpD,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAmsHD,mDAAmD;AACnD,wBAAgB,UAAU,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,iBAAiB,GAAG,SAAS,CAuG5E"}
|
|
@@ -91,11 +91,52 @@ function admissionPolicy(maxConcurrency) {
|
|
|
91
91
|
],
|
|
92
92
|
};
|
|
93
93
|
}
|
|
94
|
-
const
|
|
94
|
+
const API_TOKEN_CREDENTIAL = {
|
|
95
95
|
label: "Cloudflare API token",
|
|
96
96
|
description: "A scoped API token (My Profile → API Tokens → Create Token), not a Global API Key. Grant only the permissions the deployment needs: zone-scoped \"Zone Read\", \"Zone Settings Write\", \"DNS Write\", \"Cache Purge\", and the phase-specific Rules product Read permissions as needed; account-scoped \"Workers Scripts Read/Write\", \"Workers KV Storage Read/Write\", \"Workers R2 Storage Read/Write\", or \"Cloudflare Pages Read/Write\" for the platform tools.",
|
|
97
97
|
placeholder: "Paste API token",
|
|
98
98
|
};
|
|
99
|
+
const GLOBAL_API_KEY_CREDENTIAL = {
|
|
100
|
+
label: "Cloudflare Global API Key",
|
|
101
|
+
description: "Legacy user-scoped authentication. The key has the same access as its Cloudflare user across every account and zone that user can reach. Prefer a scoped API token when possible.",
|
|
102
|
+
fields: [
|
|
103
|
+
{
|
|
104
|
+
name: "email",
|
|
105
|
+
label: "Account email",
|
|
106
|
+
description: "The verified email address for the Cloudflare user that owns the Global API Key.",
|
|
107
|
+
placeholder: "you@example.com",
|
|
108
|
+
inputType: "email",
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
name: "apiKey",
|
|
112
|
+
label: "Global API Key",
|
|
113
|
+
description: "The legacy Global API Key from My Profile → API Tokens.",
|
|
114
|
+
placeholder: "Paste Global API Key",
|
|
115
|
+
inputType: "password",
|
|
116
|
+
},
|
|
117
|
+
],
|
|
118
|
+
};
|
|
119
|
+
function credentialConfig(authentication, override) {
|
|
120
|
+
if (authentication === "apiToken") {
|
|
121
|
+
const credential = override ?? API_TOKEN_CREDENTIAL;
|
|
122
|
+
if (credential.fields?.length) {
|
|
123
|
+
throw new Error("cloudflare() API token authentication requires a single-value credential.");
|
|
124
|
+
}
|
|
125
|
+
return credential;
|
|
126
|
+
}
|
|
127
|
+
const credential = override
|
|
128
|
+
? {
|
|
129
|
+
...GLOBAL_API_KEY_CREDENTIAL,
|
|
130
|
+
...override,
|
|
131
|
+
fields: override.fields ?? GLOBAL_API_KEY_CREDENTIAL.fields,
|
|
132
|
+
}
|
|
133
|
+
: GLOBAL_API_KEY_CREDENTIAL;
|
|
134
|
+
const fields = credential.fields?.map((field) => field.name).sort();
|
|
135
|
+
if (fields?.join(",") !== "apiKey,email") {
|
|
136
|
+
throw new Error('cloudflare() Global API Key authentication requires credential fields named "email" and "apiKey".');
|
|
137
|
+
}
|
|
138
|
+
return credential;
|
|
139
|
+
}
|
|
99
140
|
function asRecord(value) {
|
|
100
141
|
return value && typeof value === "object" && !Array.isArray(value)
|
|
101
142
|
? value
|
|
@@ -188,7 +229,7 @@ function failureFor(status, headers, errors) {
|
|
|
188
229
|
}
|
|
189
230
|
const authCoded = [...codes].some((code) => AUTH_ERROR_CODES.has(code));
|
|
190
231
|
if (status === 401 || status === 403 || authCoded) {
|
|
191
|
-
return new ConnectorCallError("auth_required", `Cloudflare rejected the
|
|
232
|
+
return new ConnectorCallError("auth_required", `Cloudflare rejected the configured credential (HTTP ${status}). ${detail} Check that it is valid and has permission to access this resource.`);
|
|
192
233
|
}
|
|
193
234
|
if (status === 400 || status === 409 || status === 422) {
|
|
194
235
|
return new ConnectorCallError("invalid_args", `Cloudflare rejected the request (HTTP ${status}). ${detail}`);
|
|
@@ -201,12 +242,25 @@ function failureFor(status, headers, errors) {
|
|
|
201
242
|
}
|
|
202
243
|
return new ConnectorCallError("connector_call_failed", `Cloudflare request failed (HTTP ${status}). ${detail}`);
|
|
203
244
|
}
|
|
204
|
-
|
|
245
|
+
const AUTHENTICATION_CONTEXT = Symbol("cloudflareAuthentication");
|
|
246
|
+
function withAuthentication(ctx, authentication) {
|
|
247
|
+
return { ...ctx, [AUTHENTICATION_CONTEXT]: authentication };
|
|
248
|
+
}
|
|
249
|
+
async function readAuthenticationHeaders(ctx) {
|
|
250
|
+
if (ctx[AUTHENTICATION_CONTEXT] === "globalApiKey") {
|
|
251
|
+
const values = await ctx.credential?.getAll();
|
|
252
|
+
const email = values?.["email"];
|
|
253
|
+
const apiKey = values?.["apiKey"];
|
|
254
|
+
if (!email || !apiKey) {
|
|
255
|
+
throw new ConnectorCallError("auth_required", "No Cloudflare Global API Key and account email are configured for this connector. An operator must add both before any call can run.");
|
|
256
|
+
}
|
|
257
|
+
return { "X-Auth-Email": email, "X-Auth-Key": apiKey };
|
|
258
|
+
}
|
|
205
259
|
const token = await ctx.credential?.get();
|
|
206
260
|
if (!token) {
|
|
207
261
|
throw new ConnectorCallError("auth_required", "No Cloudflare API token is configured for this connector. An operator must add one before any call can run.");
|
|
208
262
|
}
|
|
209
|
-
return token;
|
|
263
|
+
return { Authorization: `Bearer ${token}` };
|
|
210
264
|
}
|
|
211
265
|
function buildUrl(base, spec) {
|
|
212
266
|
const url = new URL(`${base.replace(/\/+$/, "")}${spec.path}`);
|
|
@@ -218,7 +272,7 @@ function buildUrl(base, spec) {
|
|
|
218
272
|
return url.toString();
|
|
219
273
|
}
|
|
220
274
|
async function fetchCloudflare(base, spec, ctx) {
|
|
221
|
-
const
|
|
275
|
+
const authenticationHeaders = await readAuthenticationHeaders(ctx);
|
|
222
276
|
if (spec.body !== undefined && spec.rawBody !== undefined) {
|
|
223
277
|
throw new Error("A Cloudflare request cannot have both JSON and raw bodies.");
|
|
224
278
|
}
|
|
@@ -227,7 +281,7 @@ async function fetchCloudflare(base, spec, ctx) {
|
|
|
227
281
|
response = await fetch(buildUrl(base, spec), {
|
|
228
282
|
method: spec.method,
|
|
229
283
|
headers: {
|
|
230
|
-
|
|
284
|
+
...authenticationHeaders,
|
|
231
285
|
Accept: "application/json",
|
|
232
286
|
...(spec.body !== undefined
|
|
233
287
|
? { "Content-Type": "application/json" }
|
|
@@ -263,9 +317,16 @@ async function callCloudflare(base, spec, ctx) {
|
|
|
263
317
|
if (!response.ok || envelope.success === false) {
|
|
264
318
|
throw failureFor(response.status, response.headers, errors);
|
|
265
319
|
}
|
|
320
|
+
const isV4Envelope = "success" in envelope ||
|
|
321
|
+
"result" in envelope ||
|
|
322
|
+
"result_info" in envelope ||
|
|
323
|
+
"messages" in envelope;
|
|
266
324
|
return {
|
|
267
|
-
|
|
268
|
-
|
|
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,
|
|
269
330
|
};
|
|
270
331
|
}
|
|
271
332
|
function base64FromBytes(bytes) {
|
|
@@ -787,6 +848,8 @@ function headersFromArgs(value) {
|
|
|
787
848
|
const headers = {};
|
|
788
849
|
const forbidden = new Set([
|
|
789
850
|
"authorization",
|
|
851
|
+
"x-auth-email",
|
|
852
|
+
"x-auth-key",
|
|
790
853
|
"cookie",
|
|
791
854
|
"host",
|
|
792
855
|
"content-length",
|
|
@@ -924,53 +987,82 @@ const R2_OBJECT_SCHEMA = {
|
|
|
924
987
|
},
|
|
925
988
|
required: ["key"],
|
|
926
989
|
};
|
|
927
|
-
function buildTools(scope) {
|
|
990
|
+
function buildTools(scope, authentication) {
|
|
928
991
|
const { base } = scope;
|
|
929
992
|
const zoneArg = (args) => requireScope(args["zoneId"], scope.zoneId, "zoneId");
|
|
930
993
|
const accountArg = (args) => requireScope(args["accountId"], scope.accountId, "accountId");
|
|
931
994
|
const readOnly = { readOnlyHint: true, destructiveHint: false };
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
995
|
+
const tools = [
|
|
996
|
+
authentication === "apiToken"
|
|
997
|
+
? {
|
|
998
|
+
name: "verify_api_token",
|
|
999
|
+
description: "Verify the configured Cloudflare API token and report its status. Use this first when any other tool fails with an authentication error, to separate a bad token from a missing permission.",
|
|
1000
|
+
annotations: readOnly,
|
|
1001
|
+
inputSchema: {
|
|
1002
|
+
type: "object",
|
|
1003
|
+
properties: {},
|
|
1004
|
+
required: [],
|
|
1005
|
+
additionalProperties: false,
|
|
1006
|
+
},
|
|
1007
|
+
outputSchema: {
|
|
1008
|
+
type: "object",
|
|
1009
|
+
properties: {
|
|
1010
|
+
id: { type: "string" },
|
|
1011
|
+
status: {
|
|
1012
|
+
type: "string",
|
|
1013
|
+
description: "\"active\" for a usable token.",
|
|
1014
|
+
},
|
|
1015
|
+
notBefore: { type: "string" },
|
|
1016
|
+
expiresOn: { type: "string" },
|
|
1017
|
+
},
|
|
1018
|
+
required: ["status"],
|
|
1019
|
+
},
|
|
1020
|
+
handler: async (_args, ctx) => {
|
|
1021
|
+
const { result } = await callCloudflare(base, { method: "GET", path: "/user/tokens/verify" }, ctx);
|
|
1022
|
+
const token = asRecord(result);
|
|
1023
|
+
return {
|
|
1024
|
+
id: token["id"],
|
|
1025
|
+
status: token["status"],
|
|
1026
|
+
...(token["not_before"] !== undefined
|
|
1027
|
+
? { notBefore: token["not_before"] }
|
|
1028
|
+
: {}),
|
|
1029
|
+
...(token["expires_on"] !== undefined
|
|
1030
|
+
? { expiresOn: token["expires_on"] }
|
|
1031
|
+
: {}),
|
|
1032
|
+
};
|
|
1033
|
+
},
|
|
1034
|
+
}
|
|
1035
|
+
: {
|
|
1036
|
+
name: "verify_global_api_key",
|
|
1037
|
+
description: "Verify the configured Cloudflare Global API Key and account email by retrieving the authenticated user. Use this first when another tool fails with an authentication error.",
|
|
1038
|
+
annotations: readOnly,
|
|
1039
|
+
inputSchema: {
|
|
1040
|
+
type: "object",
|
|
1041
|
+
properties: {},
|
|
1042
|
+
required: [],
|
|
1043
|
+
additionalProperties: false,
|
|
1044
|
+
},
|
|
1045
|
+
outputSchema: {
|
|
1046
|
+
type: "object",
|
|
1047
|
+
properties: {
|
|
1048
|
+
id: { type: "string" },
|
|
1049
|
+
email: { type: "string" },
|
|
1050
|
+
status: {
|
|
1051
|
+
type: "string",
|
|
1052
|
+
description: "\"active\" when Cloudflare accepts the email and key.",
|
|
1053
|
+
},
|
|
950
1054
|
},
|
|
951
|
-
|
|
952
|
-
|
|
1055
|
+
required: ["email", "status"],
|
|
1056
|
+
},
|
|
1057
|
+
handler: async (_args, ctx) => {
|
|
1058
|
+
const { result } = await callCloudflare(base, { method: "GET", path: "/user" }, ctx);
|
|
1059
|
+
const user = asRecord(result);
|
|
1060
|
+
return { id: user["id"], email: user["email"], status: "active" };
|
|
953
1061
|
},
|
|
954
|
-
required: ["status"],
|
|
955
|
-
},
|
|
956
|
-
handler: async (_args, ctx) => {
|
|
957
|
-
const { result } = await callCloudflare(base, { method: "GET", path: "/user/tokens/verify" }, ctx);
|
|
958
|
-
const token = asRecord(result);
|
|
959
|
-
return {
|
|
960
|
-
id: token["id"],
|
|
961
|
-
status: token["status"],
|
|
962
|
-
...(token["not_before"] !== undefined
|
|
963
|
-
? { notBefore: token["not_before"] }
|
|
964
|
-
: {}),
|
|
965
|
-
...(token["expires_on"] !== undefined
|
|
966
|
-
? { expiresOn: token["expires_on"] }
|
|
967
|
-
: {}),
|
|
968
|
-
};
|
|
969
1062
|
},
|
|
970
|
-
},
|
|
971
1063
|
{
|
|
972
1064
|
name: "cloudflare_api_get",
|
|
973
|
-
description: "Call any GET endpoint under Cloudflare's v4 API with this connector's
|
|
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.",
|
|
974
1066
|
annotations: readOnly,
|
|
975
1067
|
inputSchema: {
|
|
976
1068
|
type: "object",
|
|
@@ -1030,7 +1122,7 @@ function buildTools(scope) {
|
|
|
1030
1122
|
},
|
|
1031
1123
|
{
|
|
1032
1124
|
name: "cloudflare_api_mutate",
|
|
1033
|
-
description: "Call any JSON POST, PUT, PATCH, or DELETE endpoint under Cloudflare's v4 API with this connector's
|
|
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.",
|
|
1034
1126
|
annotations: { readOnlyHint: false, destructiveHint: true },
|
|
1035
1127
|
inputSchema: {
|
|
1036
1128
|
type: "object",
|
|
@@ -3132,8 +3224,12 @@ function buildTools(scope) {
|
|
|
3132
3224
|
},
|
|
3133
3225
|
},
|
|
3134
3226
|
];
|
|
3227
|
+
return tools.map((tool) => ({
|
|
3228
|
+
...tool,
|
|
3229
|
+
handler: (args, ctx) => tool.handler(args, withAuthentication(ctx, authentication)),
|
|
3230
|
+
}));
|
|
3135
3231
|
}
|
|
3136
|
-
function usageGuide(purpose, scope, instructions) {
|
|
3232
|
+
function usageGuide(purpose, scope, instructions, authentication) {
|
|
3137
3233
|
const accountInstructions = instructions?.trim();
|
|
3138
3234
|
const zoneLine = scope.zoneId
|
|
3139
3235
|
? `This connector defaults to zone \`${scope.zoneId}\`; omit \`zoneId\` unless the request names a different domain.`
|
|
@@ -3141,6 +3237,9 @@ function usageGuide(purpose, scope, instructions) {
|
|
|
3141
3237
|
const accountLine = scope.accountId
|
|
3142
3238
|
? `It defaults to account \`${scope.accountId}\`; omit \`accountId\` unless the request names a different account.`
|
|
3143
3239
|
: "It declares no default account. `list_accounts` supplies the `accountId` the Workers, KV, R2, and Pages tools need.";
|
|
3240
|
+
const authenticationLine = authentication === "apiToken"
|
|
3241
|
+
? "The API token is operator-managed and scoped by permission. An `auth_required` failure means the token is missing, invalid, or lacks that call's permission. Call `verify_api_token` first."
|
|
3242
|
+
: "The Global API Key and account email are operator-managed. The key has the same access as its Cloudflare user. An `auth_required` failure means one field is missing, the pair is invalid, or the user lacks access. Call `verify_global_api_key` first.";
|
|
3144
3243
|
return `# Cloudflare usage
|
|
3145
3244
|
|
|
3146
3245
|
Account purpose: ${purpose}
|
|
@@ -3148,11 +3247,11 @@ Account purpose: ${purpose}
|
|
|
3148
3247
|
- ${zoneLine}
|
|
3149
3248
|
- ${accountLine}
|
|
3150
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.
|
|
3151
|
-
- 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
|
|
3250
|
+
- 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.
|
|
3152
3251
|
- 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.
|
|
3153
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.
|
|
3154
3253
|
- 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.
|
|
3155
|
-
-
|
|
3254
|
+
- ${authenticationLine}
|
|
3156
3255
|
- 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.
|
|
3157
3256
|
- Named creates that only add a resource are write-routed without claiming destruction. Updates, overwrites, deletes, rollbacks, cache purges, \`cloudflare_api_mutate\`, and \`cloudflare_api_upload\` are destructive. Read current state before changing it, and prefer a targeted \`purge_cache\` over \`everything\`.
|
|
3158
3257
|
${accountInstructions
|
|
@@ -3169,6 +3268,10 @@ export function cloudflare(id, options) {
|
|
|
3169
3268
|
if (!Number.isInteger(maxConcurrency) || maxConcurrency < 1) {
|
|
3170
3269
|
throw new Error("cloudflare() maxConcurrency must be a positive integer.");
|
|
3171
3270
|
}
|
|
3271
|
+
const authentication = options.authentication ?? "apiToken";
|
|
3272
|
+
if (authentication !== "apiToken" && authentication !== "globalApiKey") {
|
|
3273
|
+
throw new Error('cloudflare() authentication must be "apiToken" or "globalApiKey".');
|
|
3274
|
+
}
|
|
3172
3275
|
const scope = {
|
|
3173
3276
|
base: options.baseUrl?.trim() || CLOUDFLARE_API_BASE,
|
|
3174
3277
|
accountId: options.accountId?.trim() || undefined,
|
|
@@ -3177,34 +3280,64 @@ export function cloudflare(id, options) {
|
|
|
3177
3280
|
return api(id, {
|
|
3178
3281
|
title: options.title ?? "Cloudflare",
|
|
3179
3282
|
description: `Cloudflare control-plane access for zones, DNS, Workers, KV, R2, Pages, media, email, and other v4 APIs — ${purpose}`,
|
|
3180
|
-
credential: options.credential
|
|
3283
|
+
credential: credentialConfig(authentication, options.credential),
|
|
3181
3284
|
callAdmission: admissionPolicy(maxConcurrency),
|
|
3182
|
-
usageGuide: usageGuide(purpose, scope, options.instructions),
|
|
3285
|
+
usageGuide: usageGuide(purpose, scope, options.instructions, authentication),
|
|
3183
3286
|
// The schemas are hand-written and closed; a schema that cannot be
|
|
3184
3287
|
// enforced is a bug in this file, not input to pass through.
|
|
3185
3288
|
strictValidation: true,
|
|
3186
3289
|
...(options.maxResultBytes !== undefined
|
|
3187
3290
|
? { maxResultBytes: options.maxResultBytes }
|
|
3188
3291
|
: {}),
|
|
3189
|
-
tools: buildTools(scope),
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3292
|
+
tools: buildTools(scope, authentication),
|
|
3293
|
+
...(authentication === "apiToken"
|
|
3294
|
+
? {
|
|
3295
|
+
async testCredential(value, ctx) {
|
|
3296
|
+
try {
|
|
3297
|
+
const { result } = await callCloudflare(scope.base, { method: "GET", path: "/user/tokens/verify" }, withAuthentication({
|
|
3298
|
+
...ctx,
|
|
3299
|
+
credential: {
|
|
3300
|
+
get: async () => value,
|
|
3301
|
+
getAll: async () => ({ value }),
|
|
3302
|
+
},
|
|
3303
|
+
}, authentication));
|
|
3304
|
+
const status = asRecord(result)["status"];
|
|
3305
|
+
return status === "active"
|
|
3306
|
+
? { ok: true, message: "Token verified: active." }
|
|
3307
|
+
: { ok: false, message: `Token status is "${String(status)}".` };
|
|
3308
|
+
}
|
|
3309
|
+
catch (error) {
|
|
3310
|
+
return {
|
|
3311
|
+
ok: false,
|
|
3312
|
+
message: error instanceof Error ? error.message : String(error),
|
|
3313
|
+
};
|
|
3314
|
+
}
|
|
3315
|
+
},
|
|
3206
3316
|
}
|
|
3207
|
-
|
|
3317
|
+
: {
|
|
3318
|
+
async testCredentials(values, ctx) {
|
|
3319
|
+
try {
|
|
3320
|
+
const { result } = await callCloudflare(scope.base, { method: "GET", path: "/user" }, withAuthentication({
|
|
3321
|
+
...ctx,
|
|
3322
|
+
credential: {
|
|
3323
|
+
get: async (field) => field ? values[field] ?? null : null,
|
|
3324
|
+
getAll: async () => values,
|
|
3325
|
+
},
|
|
3326
|
+
}, authentication));
|
|
3327
|
+
const email = asRecord(result)["email"];
|
|
3328
|
+
return {
|
|
3329
|
+
ok: true,
|
|
3330
|
+
message: `Global API Key verified for ${String(email)}.`,
|
|
3331
|
+
};
|
|
3332
|
+
}
|
|
3333
|
+
catch (error) {
|
|
3334
|
+
return {
|
|
3335
|
+
ok: false,
|
|
3336
|
+
message: error instanceof Error ? error.message : String(error),
|
|
3337
|
+
};
|
|
3338
|
+
}
|
|
3339
|
+
},
|
|
3340
|
+
}),
|
|
3208
3341
|
});
|
|
3209
3342
|
}
|
|
3210
3343
|
//# sourceMappingURL=cloudflare.js.map
|