@zackbart/connecta 0.29.0 → 0.31.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 (116) hide show
  1. package/CHANGELOG.md +99 -0
  2. package/README.md +126 -134
  3. package/bin/connecta.mjs +13 -2
  4. package/bin/version.mjs +1 -1
  5. package/dist/argument-redaction.js +230 -0
  6. package/dist/auth-elicitation.js +14 -9
  7. package/dist/authorization-handoff.js +111 -0
  8. package/dist/call-recovery.js +25 -0
  9. package/dist/catalog-cache.js +2 -2
  10. package/dist/catalog-service.js +103 -63
  11. package/dist/config-schema.d.ts +14 -7
  12. package/dist/config-schema.js +29 -10
  13. package/dist/config.d.ts +2 -0
  14. package/dist/config.js +12 -7
  15. package/dist/connectors/guarded-fetch.d.ts +9 -1
  16. package/dist/connectors/guarded-fetch.js +17 -5
  17. package/dist/connectors/negotiation-cache.js +1 -1
  18. package/dist/downstream-input-context.js +9 -0
  19. package/dist/downstream-input.js +13 -11
  20. package/dist/errors.d.ts +38 -12
  21. package/dist/errors.js +19 -9
  22. package/dist/execute.js +375 -43
  23. package/dist/executor-result.js +11 -9
  24. package/dist/executors/quickjs-child.js +12 -5
  25. package/dist/executors/quickjs-protocol.js +5 -6
  26. package/dist/executors/quickjs-runtime.js +6 -1
  27. package/dist/executors/quickjs.js +20 -4
  28. package/dist/guest-runtime.js +52 -2
  29. package/dist/guest-types.d.ts +47 -5
  30. package/dist/index.d.ts +1 -1
  31. package/dist/invocation.js +72 -14
  32. package/dist/meta-output.js +0 -15
  33. package/dist/meta-tools.js +166 -95
  34. package/dist/oauth-handoff.js +88 -11
  35. package/dist/operator-ui/generated.js +2 -2
  36. package/dist/operator-ui/view.d.ts +9 -0
  37. package/dist/operator-ui/view.js +11 -0
  38. package/dist/program-result.js +5 -0
  39. package/dist/program-writes.js +7 -48
  40. package/dist/provider.d.ts +8 -6
  41. package/dist/provider.js +3 -4
  42. package/dist/providers/_shared/google/workspace.d.ts +1 -1
  43. package/dist/providers/_shared/request-token.js +83 -0
  44. package/dist/providers/_shared/rest/dispatch.js +41 -0
  45. package/dist/providers/_shared/rest/operation-index.d.ts +133 -0
  46. package/dist/providers/_shared/rest/operation-index.js +527 -0
  47. package/dist/providers/_shared/rest/tools.d.ts +141 -0
  48. package/dist/providers/_shared/rest/tools.js +569 -0
  49. package/dist/providers/_shared/rest/value-safety.d.ts +151 -0
  50. package/dist/providers/_shared/rest/value-safety.js +580 -0
  51. package/dist/providers/cloudflare/graphql.js +385 -0
  52. package/dist/providers/cloudflare/index.d.ts +47 -51
  53. package/dist/providers/cloudflare/index.js +188 -1997
  54. package/dist/providers/cloudflare/openapi.generated.js +3347 -0
  55. package/dist/providers/cloudflare/rest.d.ts +37 -0
  56. package/dist/providers/cloudflare/rest.js +1056 -0
  57. package/dist/providers/cloudflare/skill.generated.js +7 -4
  58. package/dist/providers/cloudflare/value-safety.js +1247 -0
  59. package/dist/providers/infisical/index.d.ts +7 -0
  60. package/dist/providers/infisical/index.js +713 -0
  61. package/dist/providers/infisical/skill.generated.js +8 -0
  62. package/dist/providers/notion/index.d.ts +35 -31
  63. package/dist/providers/notion/index.js +60 -1499
  64. package/dist/providers/notion/openapi.generated.js +75 -0
  65. package/dist/providers/notion/rest.d.ts +24 -0
  66. package/dist/providers/notion/rest.js +1067 -0
  67. package/dist/providers/notion/skill.generated.js +5 -4
  68. package/dist/providers/notion/value-safety.js +31 -0
  69. package/dist/providers/stripe/index.d.ts +26 -20
  70. package/dist/providers/stripe/index.js +103 -130
  71. package/dist/providers/stripe/openapi.generated.js +649 -0
  72. package/dist/providers/stripe/rest.d.ts +14 -0
  73. package/dist/providers/stripe/rest.js +395 -0
  74. package/dist/providers/stripe/skill.generated.js +7 -5
  75. package/dist/providers/stripe/value-safety.js +705 -0
  76. package/dist/providers/vercel/index.d.ts +31 -22
  77. package/dist/providers/vercel/index.js +96 -853
  78. package/dist/providers/vercel/openapi.generated.js +456 -0
  79. package/dist/providers/vercel/rest.d.ts +20 -0
  80. package/dist/providers/vercel/rest.js +1002 -0
  81. package/dist/providers/vercel/skill.generated.js +4 -5
  82. package/dist/providers/vercel/value-safety.js +328 -0
  83. package/dist/registry.d.ts +8 -6
  84. package/dist/registry.js +134 -61
  85. package/dist/resource-read.js +4 -1
  86. package/dist/routes/mcp.js +11 -2
  87. package/dist/sent-secrets.d.ts +54 -13
  88. package/dist/sent-secrets.js +770 -231
  89. package/dist/skills.js +26 -16
  90. package/dist/sqlite.js +21 -6
  91. package/dist/storage/keys.js +20 -5
  92. package/dist/storage/memory.js +9 -4
  93. package/dist/storage/sql.js +18 -9
  94. package/dist/types.d.ts +17 -3
  95. package/dist/usage-guide.js +49 -40
  96. package/dist/version.d.ts +1 -1
  97. package/dist/version.js +1 -1
  98. package/dist/worker.js +2 -1
  99. package/dist/write-outcome.js +47 -0
  100. package/documentation/README.md +64 -0
  101. package/documentation/architecture.md +241 -97
  102. package/documentation/auth.md +138 -5
  103. package/documentation/code-mode.md +224 -107
  104. package/documentation/deploying.md +146 -0
  105. package/documentation/integrating.md +170 -0
  106. package/documentation/meta-tools.md +142 -30
  107. package/documentation/operating.md +135 -0
  108. package/documentation/operator-tests.md +4 -0
  109. package/documentation/operator-ui.md +132 -0
  110. package/documentation/provider-auth-migration.md +307 -0
  111. package/documentation/provider-migration-0.29.md +22 -5
  112. package/examples/worker/README.md +9 -2
  113. package/package.json +9 -2
  114. package/templates/node/README.md +9 -2
  115. package/templates/node/package.json +1 -1
  116. package/dist/providers/vercel/mcp-ownership.js +0 -212
@@ -0,0 +1,14 @@
1
+ import type { ApiTool } from "../../connectors/api-connector.js";
2
+ import type { ConnectorContext, CredentialTestResult } from "../../types.js";
3
+ import { type RestVendor } from "../_shared/rest/tools.js";
4
+ /** Which Stripe environment a key reaches. */
5
+ export type StripeMode = "production" | "sandbox";
6
+ /** Stripe's REST origin. */
7
+ export declare const STRIPE_API_BASE_URL = "https://api.stripe.com";
8
+ export interface StripeRest {
9
+ vendor: RestVendor;
10
+ tools: ApiTool[];
11
+ testCredential(value: string, ctx: ConnectorContext): Promise<CredentialTestResult>;
12
+ }
13
+ /** Stripe's REST vendor, its named tools, and its credential test, for one mode. */
14
+ export declare function stripeRest(mode: StripeMode, connectedAccount: string | undefined): StripeRest;
@@ -0,0 +1,395 @@
1
+ import { retryAfterMs } from "../../connectors/guarded-fetch.js";
2
+ import { ConnectorCallError } from "../../errors.js";
3
+ import { OperationIndex } from "../_shared/rest/operation-index.js";
4
+ import { callRest, restCall, restTransport, } from "../_shared/rest/tools.js";
5
+ import { valueSafety } from "../_shared/rest/value-safety.js";
6
+ import { openapi } from "./openapi.generated.js";
7
+ import { STRIPE_VALUE_SAFETY } from "./value-safety.js";
8
+ /** Stripe's REST origin. */
9
+ export const STRIPE_API_BASE_URL = "https://api.stripe.com";
10
+ const FILES_ORIGIN = "https://files.stripe.com";
11
+ const METER_EVENTS_ORIGIN = "https://meter-events.stripe.com";
12
+ const MAX_RESPONSE_BYTES = 8 * 1024 * 1024;
13
+ const READ_CAP_MS = 60_000;
14
+ /**
15
+ * POSTs that only read, admitted by `stripe_api_read`. `POST
16
+ * /v1/tax/calculations` is deliberately absent: it persists a Calculation
17
+ * object a later Tax Transaction can reference, and Stripe bills each call.
18
+ */
19
+ const READ_POSTS = [
20
+ ["POST", "/v1/invoices/create_preview", "Computes an upcoming invoice; Stripe persists nothing."],
21
+ ];
22
+ function record(value) {
23
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
24
+ }
25
+ function text(value) {
26
+ return typeof value === "string" && value.trim() !== "" ? value : undefined;
27
+ }
28
+ function invalid(message) {
29
+ throw new ConnectorCallError("invalid_args", message);
30
+ }
31
+ /**
32
+ * Why this key cannot serve a connector declared for `mode`, or undefined.
33
+ * The key's own prefix carries its mode, so a live key in a sandbox
34
+ * connector (or the reverse) is refused before any request leaves.
35
+ */
36
+ function stripeKeyProblem(key, mode) {
37
+ if (/^(?:sk|rk)_org_/.test(key)) {
38
+ return "This is a Stripe organization key, which needs a Stripe-Context header this connector does not send. Use one account's secret or restricted key.";
39
+ }
40
+ if (key.startsWith("pk_")) {
41
+ return "This is a publishable key; the Stripe API needs a secret (sk_) or restricted (rk_) key.";
42
+ }
43
+ const prefix = /^(?:sk|rk)_(live|test)_\S+$/.exec(key);
44
+ if (!prefix)
45
+ return "This is not a Stripe secret (sk_…) or restricted (rk_…) key.";
46
+ const keyMode = prefix[1] === "live" ? "production" : "sandbox";
47
+ if (keyMode !== mode) {
48
+ return `This connector is declared ${mode}, but the configured key is a ${prefix[1]}-mode key.`;
49
+ }
50
+ return undefined;
51
+ }
52
+ /** Flatten one value Stripe's way: `a[b]=`, indexed arrays, `""` to unset. */
53
+ function flatten(key, value, emit, emptyArrays) {
54
+ if (value === undefined)
55
+ return;
56
+ if (value === null || value === "")
57
+ return emit(key, "");
58
+ if (Array.isArray(value)) {
59
+ if (value.length === 0) {
60
+ if (emptyArrays)
61
+ emit(key, "");
62
+ return;
63
+ }
64
+ value.forEach((item, index) => flatten(`${key}[${index}]`, item, emit, emptyArrays));
65
+ return;
66
+ }
67
+ if (typeof value === "object") {
68
+ for (const [name, item] of Object.entries(value))
69
+ flatten(`${key}[${name}]`, item, emit, emptyArrays);
70
+ return;
71
+ }
72
+ if (typeof value === "boolean")
73
+ return emit(key, value ? "true" : "false");
74
+ if (typeof value === "number" && Number.isFinite(value))
75
+ return emit(key, String(value));
76
+ if (typeof value === "string")
77
+ return emit(key, value);
78
+ invalid(`Stripe parameter ${key} must be a string, number, boolean, object, list, or null.`);
79
+ }
80
+ /**
81
+ * A v1 form body: nested objects as `a[b]`, arrays indexed `a[0]`, booleans
82
+ * as `true`/`false`, and `null`, `""`, or `[]` as an empty value, which is
83
+ * how Stripe unsets a field.
84
+ */
85
+ function stripeForm(body) {
86
+ const form = new URLSearchParams();
87
+ for (const [key, value] of Object.entries(body))
88
+ flatten(key, value, (name, item) => form.append(name, item), true);
89
+ return form.toString();
90
+ }
91
+ /**
92
+ * Query parameters. v1 indexes arrays (`expand[0]=…`) like its bodies; v2
93
+ * repeats the key for a list of scalars (`include=a&include=b`), as Stripe's
94
+ * own libraries do. Objects use brackets in both.
95
+ */
96
+ function stripeQuery(query, v2) {
97
+ const out = {};
98
+ for (const [key, value] of Object.entries(query)) {
99
+ const scalars = Array.isArray(value) && value.every((item) => ["string", "number", "boolean"].includes(typeof item));
100
+ if (v2 && scalars && value.length > 0) {
101
+ out[key] = value.map((item) => (typeof item === "boolean" ? (item ? "true" : "false") : String(item)));
102
+ continue;
103
+ }
104
+ flatten(key, value, (name, item) => (out[name] = item), false);
105
+ }
106
+ return out;
107
+ }
108
+ function expansions(call) {
109
+ const read = (value) => typeof value === "string"
110
+ ? [value]
111
+ : Array.isArray(value)
112
+ ? value.filter((item) => typeof item === "string")
113
+ : [];
114
+ return [...read(call.query["expand"]), ...read(record(call.body)["expand"])];
115
+ }
116
+ /**
117
+ * Reviewed refusals that depend on arguments; the value-safety table
118
+ * (`value-safety.ts`) refuses whole operations. Each expansion returns a
119
+ * secret the caller could not otherwise read through Connecta: a Stripe Apps
120
+ * secret's value, or a full Issuing card number or CVC, which an Issuing
121
+ * authorization, transaction, or token can embed as well as the card itself.
122
+ */
123
+ function refuse(call) {
124
+ const expanded = expansions(call);
125
+ if (/^\/v1\/apps\/secrets(?:\/|$)/.test(call.path) && expanded.some((path) => /(?:^|\.)payload$/.test(path))) {
126
+ return "Stripe Apps secret values are not returned through Connecta. Read the secret's metadata without expanding payload.";
127
+ }
128
+ if (/^\/v1\/(?:test_helpers\/)?issuing\//.test(call.path) &&
129
+ expanded.some((path) => /(?:^|\.)(?:number|cvc)$/.test(path))) {
130
+ return "Issuing card numbers and CVCs are not expanded through Connecta. Use the card id and last4, or the Stripe Dashboard.";
131
+ }
132
+ return undefined;
133
+ }
134
+ /**
135
+ * Map a Stripe failure by the caller's next move (H11). For an operation the
136
+ * value-safety review withholds (`withheld`), Stripe's message is replaced:
137
+ * the type, code, decline code, and param still route.
138
+ */
139
+ function stripeFailure(status, headers, body, withheld = false) {
140
+ const error = record(record(body)["error"]);
141
+ const code = text(error["code"]);
142
+ const kind = [text(error["type"]), code, text(error["decline_code"])].filter(Boolean).join(" ");
143
+ const param = text(error["param"]);
144
+ const requestId = headers.get("request-id");
145
+ const message = withheld
146
+ ? "Stripe's message is withheld because this operation handles credentials."
147
+ : (text(error["message"]) ?? `Stripe returned HTTP ${status}.`);
148
+ const detail = `Stripe ${kind || `HTTP ${status}`}${param ? ` (param ${param})` : ""}: ` +
149
+ `${message}${requestId ? ` Request ${requestId}.` : ""}`;
150
+ if (status === 429) {
151
+ if (code === "lock_timeout") {
152
+ // Stripe did not process the request (docs.stripe.com/rate-limits#object-lock-timeouts):
153
+ // a definite conflict, safe to retry once the other request finishes.
154
+ return new ConnectorCallError("conflict", `${detail} Another request is changing this object and Stripe did not process this one; retry after it finishes. This is a lock conflict, not a rate limit.`, { retryable: true, retryAfterMs: 1_000 });
155
+ }
156
+ const reason = headers.get("stripe-rate-limited-reason");
157
+ return new ConnectorCallError("rate_limited", `${detail}${reason ? ` Limited by ${reason}.` : ""} Back off before retrying.`, { retryAfterMs: retryAfterMs(headers) ?? 1_000 });
158
+ }
159
+ if (status === 401) {
160
+ return new ConnectorCallError("auth_required", `${detail} The configured Stripe key was rejected; an operator must replace it in this connection.`);
161
+ }
162
+ if (status === 403) {
163
+ return new ConnectorCallError("provider_permission_denied", `${detail} A restricted key lacks this permission; an operator must grant it on the key or use another key.`);
164
+ }
165
+ if (status === 404)
166
+ return new ConnectorCallError("not_found", `${detail} Resolve the id with a list or search read.`);
167
+ if (status === 400 || status === 409 || status === 422)
168
+ return new ConnectorCallError("invalid_args", detail);
169
+ if (status === 402)
170
+ return new ConnectorCallError("connector_call_failed", detail, { retryable: false });
171
+ if (status === 424 || status >= 500)
172
+ return new ConnectorCallError("unavailable", detail);
173
+ return new ConnectorCallError("connector_call_failed", detail, { retryable: false });
174
+ }
175
+ /** Cursors: v1 lists, search results, and v2 lists each spell the next page differently. */
176
+ function page(data, call) {
177
+ const body = record(data);
178
+ if (body["object"] === "list" && Array.isArray(body["data"])) {
179
+ const backward = call.query["ending_before"] !== undefined;
180
+ const items = body["data"];
181
+ const edge = record(backward ? items[0] : items[items.length - 1])["id"];
182
+ const hasMore = body["has_more"] === true;
183
+ return {
184
+ hasMore,
185
+ ...(hasMore && typeof edge === "string"
186
+ ? { next: edge, param: backward ? "ending_before" : "starting_after" }
187
+ : {}),
188
+ };
189
+ }
190
+ if (body["object"] === "search_result") {
191
+ const next = text(body["next_page"]);
192
+ return { hasMore: body["has_more"] === true, ...(next ? { next, param: "page" } : {}) };
193
+ }
194
+ if (Array.isArray(body["data"]) && "next_page_url" in body) {
195
+ const url = text(body["next_page_url"]);
196
+ let next = null;
197
+ try {
198
+ next = url ? new URL(url, STRIPE_API_BASE_URL).searchParams.get("page") : null;
199
+ }
200
+ catch {
201
+ next = null;
202
+ }
203
+ return { hasMore: url !== undefined, ...(next ? { next, param: "page" } : {}) };
204
+ }
205
+ return undefined;
206
+ }
207
+ /** The pinned index, shared by every Stripe REST connector in the deployment. */
208
+ let index;
209
+ function stripeIndex() {
210
+ index ??= new OperationIndex(openapi, { vendor: "stripe", title: "Stripe" });
211
+ return index;
212
+ }
213
+ /** The reviewed value-safety table over the pinned index. */
214
+ const SAFETY = valueSafety(STRIPE_VALUE_SAFETY, () => stripeIndex());
215
+ /** Stripe's REST vendor, its named tools, and its credential test, for one mode. */
216
+ export function stripeRest(mode, connectedAccount) {
217
+ const operations = stripeIndex();
218
+ const transports = new Map();
219
+ const transport = (origin) => {
220
+ let send = transports.get(origin);
221
+ if (!send) {
222
+ send = restTransport({
223
+ provider: "Stripe",
224
+ baseUrl: origin,
225
+ maxResponseBytes: MAX_RESPONSE_BYTES,
226
+ timeoutMs: READ_CAP_MS,
227
+ authenticate: async (ctx) => {
228
+ const key = (await ctx.credential?.get())?.trim();
229
+ if (!key) {
230
+ throw new ConnectorCallError("auth_required", "No Stripe key is configured for this connector. An operator must add one in this connection in the operator UI.");
231
+ }
232
+ const problem = stripeKeyProblem(key, mode);
233
+ if (problem) {
234
+ throw new ConnectorCallError("auth_required", `${problem} An operator must replace this connector's key.`);
235
+ }
236
+ return { Authorization: `Bearer ${key}` };
237
+ },
238
+ });
239
+ transports.set(origin, send);
240
+ }
241
+ return send;
242
+ };
243
+ const encode = (call) => {
244
+ const v2 = call.path.startsWith("/v2/");
245
+ const headers = { "Stripe-Version": operations.version };
246
+ // v2 names the acting account with Stripe-Context; v1 with Stripe-Account.
247
+ if (connectedAccount)
248
+ headers[v2 ? "Stripe-Context" : "Stripe-Account"] = connectedAccount;
249
+ const query = stripeQuery(call.query, v2);
250
+ if (call.body === undefined)
251
+ return { query, headers };
252
+ if (v2)
253
+ return { query, headers, body: call.body };
254
+ if (typeof call.body !== "object" || call.body === null || Array.isArray(call.body)) {
255
+ invalid("A Stripe v1 body is an object of parameters.");
256
+ }
257
+ return {
258
+ query,
259
+ headers: { ...headers, "Content-Type": "application/x-www-form-urlencoded" },
260
+ rawBody: stripeForm(call.body),
261
+ };
262
+ };
263
+ const vendor = {
264
+ vendor: "stripe",
265
+ title: "Stripe",
266
+ index: operations,
267
+ transport(server) {
268
+ if (server === undefined)
269
+ return transport(STRIPE_API_BASE_URL);
270
+ if (server === FILES_ORIGIN)
271
+ return transport(FILES_ORIGIN);
272
+ if (server === METER_EVENTS_ORIGIN) {
273
+ return "The meter event stream takes a short-lived meter event session token, not an API key. Send single events with POST /v2/billing/meter_events.";
274
+ }
275
+ return `This operation is served from ${server}, which this connector does not reach.`;
276
+ },
277
+ failure: (status, headers, body, call) => stripeFailure(status, headers, body, call ? SAFETY.withholdsErrors(call.op) : false),
278
+ readPosts: READ_POSTS,
279
+ valueSafety: SAFETY,
280
+ refuse,
281
+ encode,
282
+ page,
283
+ idempotencyHeader: "Idempotency-Key",
284
+ upload: "Upload files in the Stripe Dashboard; this connector sends no multipart bodies.",
285
+ bodyHint: "v1 bodies are form-encoded for you, v2 bodies sent as JSON",
286
+ };
287
+ const livemode = mode === "production";
288
+ const tools = [
289
+ {
290
+ name: "get_stripe_account_info",
291
+ description: "Read the Stripe account this key reaches: id, name, country, default currency, mode, and connected account. Use it to confirm scope first.",
292
+ annotations: { readOnlyHint: true },
293
+ inputSchema: { type: "object", properties: {}, required: [], additionalProperties: false },
294
+ outputSchema: {
295
+ type: "object",
296
+ properties: {
297
+ accountId: { type: "string" },
298
+ name: { type: ["string", "null"] },
299
+ country: { type: ["string", "null"] },
300
+ defaultCurrency: { type: ["string", "null"] },
301
+ mode: { type: "string", enum: ["production", "sandbox"] },
302
+ livemode: { type: "boolean" },
303
+ connectedAccount: { type: ["string", "null"], description: "Set when every call acts as this account." },
304
+ chargesEnabled: { type: ["boolean", "null"] },
305
+ payoutsEnabled: { type: ["boolean", "null"] },
306
+ },
307
+ required: ["accountId", "mode", "livemode", "connectedAccount"],
308
+ },
309
+ handler: async (_args, ctx) => {
310
+ const { data } = await callRest(vendor, restCall(vendor, "GET", "/v1/account"), ctx);
311
+ const account = record(data);
312
+ const profile = record(account["business_profile"]);
313
+ const dashboard = record(record(account["settings"])["dashboard"]);
314
+ return {
315
+ accountId: String(account["id"] ?? ""),
316
+ name: text(profile["name"]) ?? text(dashboard["display_name"]) ?? null,
317
+ country: text(account["country"]) ?? null,
318
+ defaultCurrency: text(account["default_currency"]) ?? null,
319
+ mode,
320
+ livemode,
321
+ connectedAccount: connectedAccount ?? null,
322
+ chargesEnabled: typeof account["charges_enabled"] === "boolean" ? account["charges_enabled"] : null,
323
+ payoutsEnabled: typeof account["payouts_enabled"] === "boolean" ? account["payouts_enabled"] : null,
324
+ };
325
+ },
326
+ },
327
+ {
328
+ name: "get_balance_summary",
329
+ description: "Read the account's available and pending Stripe balance per currency, in minor units. Excludes balance transactions; list those with stripe_api_read.",
330
+ annotations: { readOnlyHint: true },
331
+ inputSchema: { type: "object", properties: {}, required: [], additionalProperties: false },
332
+ outputSchema: {
333
+ type: "object",
334
+ properties: {
335
+ livemode: { type: "boolean" },
336
+ available: { type: "array", description: "Funds available to pay out, per currency." },
337
+ pending: { type: "array", description: "Funds not yet available, per currency." },
338
+ instantAvailable: { type: "array" },
339
+ connectReserved: { type: "array" },
340
+ },
341
+ required: ["livemode", "available", "pending"],
342
+ },
343
+ handler: async (_args, ctx) => {
344
+ const { data } = await callRest(vendor, restCall(vendor, "GET", "/v1/balance"), ctx);
345
+ const balance = record(data);
346
+ const amounts = (value) => Array.isArray(value)
347
+ ? value.map((entry) => ({ amount: record(entry)["amount"], currency: record(entry)["currency"] }))
348
+ : [];
349
+ return {
350
+ livemode: balance["livemode"] === true,
351
+ available: amounts(balance["available"]),
352
+ pending: amounts(balance["pending"]),
353
+ ...(Array.isArray(balance["instant_available"])
354
+ ? { instantAvailable: amounts(balance["instant_available"]) }
355
+ : {}),
356
+ ...(Array.isArray(balance["connect_reserved"])
357
+ ? { connectReserved: amounts(balance["connect_reserved"]) }
358
+ : {}),
359
+ };
360
+ },
361
+ },
362
+ ];
363
+ return {
364
+ vendor,
365
+ tools,
366
+ async testCredential(value, ctx) {
367
+ const key = value.trim();
368
+ const problem = stripeKeyProblem(key, mode);
369
+ if (problem)
370
+ return { ok: false, message: problem };
371
+ try {
372
+ const { data } = await callRest(vendor, restCall(vendor, "GET", "/v1/account"), {
373
+ ...ctx,
374
+ credential: { get: async () => key, getAll: async () => ({ value: key }) },
375
+ });
376
+ const account = record(data);
377
+ const name = text(record(account["business_profile"])["name"]);
378
+ return {
379
+ ok: true,
380
+ message: `Authenticated to ${String(account["id"] ?? "the Stripe account")}${name ? ` (${name})` : ""} with a ${livemode ? "live" : "test"}-mode key.`,
381
+ };
382
+ }
383
+ catch (error) {
384
+ if (error instanceof ConnectorCallError && error.code === "provider_permission_denied") {
385
+ // Stripe authenticated the key; only the account read is outside its permissions.
386
+ return {
387
+ ok: true,
388
+ message: `Stripe accepted the ${livemode ? "live" : "test"}-mode key; it cannot read the account object, which only get_stripe_account_info needs.`,
389
+ };
390
+ }
391
+ return { ok: false, message: error instanceof ConnectorCallError ? error.message : "Stripe rejected the key." };
392
+ }
393
+ },
394
+ };
395
+ }
@@ -1,11 +1,13 @@
1
1
  // Generated by scripts/generate-providers.mjs; do not edit.
2
2
  export const skill = {
3
- "content": "\n\nThis OAuth session may expose both live and sandbox Stripe accounts. Discover the live account-selection tools, select the intended account and mode, then carry exact context fields such as `stripe_context` and `livemode` wherever the schema requires them. A live-mode write moves real money; a sandbox write changes test data. Never infer the account or mode from connector metadata.\n\n- Discover account and mode with the live account-selection tools before every account-scoped read or write. The published inventory has changed account-tool names; do not assume `list_available_accounts_or_orgs` is available. Select the intended result and carry the exact context fields required by the live schema unchanged. If the account, mode, or supported selector is ambiguous, stop and ask; never guess.\n- Organization accounts are not Stripe Connect connected accounts. A Connect call requires a separate connector with a deployment-configured restricted key plus Stripe's documented `Stripe-Account` header; OAuth does not support that path.\n\n\n- Organization accounts are not Stripe Connect connected accounts. A Connect call requires a deployment-configured restricted key plus Stripe's documented `Stripe-Account` header; OAuth does not support that path. Do not try to turn an organization-account call into a Connect call inside tool arguments.\n\n- Four generic tools reach any Stripe API method. Find the method with `stripe_api_search`, read its parameters with `stripe_api_details`, then call `stripe_api_read` (GET) or `stripe_api_write` (POST/PATCH/PUT/DELETE). Never guess a path or a parameter name — `stripe_api_details` is cheaper than a rejected write.\n- Prefer a dedicated tool when one covers the task: `get_balance_summary` for balances and `stripe_analytics` for Sigma or Metrics reporting. Use `stripe_api_search` for everything else instead of assuming a retired dedicated tool still exists.\n- `stripe_implementation_planner` and the query-execution intents of `stripe_analytics` create provider-side planning or query-run state. Connecta therefore classifies both as writes and enforces the configured pool trust policy; their retrieval paths stay behind the same tool boundary.\n- `stripe_api_write` carries the blast radius of the entire write API — every POST, PATCH, PUT, and DELETE, from a customer edit to a subscription cancellation. State the method and path explicitly; execution follows the configured pool trust policy.\n- Lists are cursor-paginated: `limit` defaults to 10 and caps at 100, `starting_after` and `ending_before` take an object id and are mutually exclusive, and `has_more` says whether to continue. Page inside `execute_code`.\n- Any `stripe_api_read` list or `stripe_api_search` that returns full objects belongs inside `execute_code`, projected to the fields the question needs before `return`. Neither `limit` nor `expand` substitutes for that: an unprojected list of customers or invoices truncates long before it answers, and a projected one keeps the customer's name, email, and address out of the transcript.\n- Search filters on a documented per-resource field set, not on arbitrary attributes. Charges search takes `amount`, `created`, `currency`, `customer`, `status`, `refunded`, `disputed`, `metadata`, `billing_details.address.postal_code`, and `payment_method_details.<source>.*` card fields — there is no `payment_intent` field. When the field you want is not searchable, retrieve the parent object and follow its reference (the PaymentIntent's `latest_charge`) instead of retrying the search with another spelling.\n- The account → `stripe_api_search` → `stripe_api_details` → `stripe_api_read` sequence is one program, not four turns: resolve the method once, then call that method as many times as the investigation needs in the same run.\n- Decline outcomes live on the charge — `outcome`, `failure_code`, `failure_message` — reached from the PaymentIntent's `latest_charge`, so \"why did this payment fail\" is a PaymentIntent read followed by one charge read.\n- Resolve ids before acting; never guess one. Stripe ids are typed prefixes — `cus_` customer, `sub_` subscription, `ch_` charge, `pi_` payment intent, `in_` invoice, `acct_` account — and a plausible-looking id belongs to a different object or to nobody. Find the object with `stripe_api_search` (or a list endpoint through `stripe_api_read`) and carry the `id` it returned into the write.\n- This connection's tool list is not a fixed set. Stripe gates parts of its MCP catalog by account, integration, and beta enrollment, so search this connector for what it actually exposes rather than assuming a documented tool is here.\n- Amounts are integers in the currency's minor unit: `1099` is 10.99 USD, and zero-decimal currencies like JPY take `10` for 10 JPY. Never send a decimal.\n- Send an `Idempotency-Key` on every write you might retry, if the tool accepts it, and reuse the same key for the retry. A retry with a fresh key is a second charge, not a second attempt.\n- Stripe answers a rate limit with `429` and a `Stripe-Rate-Limited-Reason` header; back off on that rather than retrying immediately. Stripe documents an account ceiling of , and any single endpoint is capped at 25 per second regardless of mode, so paging one list is the real constraint.\n- Use `search_stripe_documentation` when the shape of an object or a flow is unclear; it is a read and costs nothing but a call.\n- Treat every create, update, delete, refund, and report run as a write. For guest calls, classification, and routing, fetch `skills({ name: \"usage\" })`.\n- An `auth_required` failure means this connector's Stripe authorization is missing or expired: run `authorize_connector` for this connector id, then retry the same call unchanged. A rejected argument or a plan restriction comes back in Stripe's own words instead — read it rather than re-authorizing.\n",
3
+ "content": "- Lists are cursor-paginated: `limit` defaults to 10 and caps at 100, `starting_after` and `ending_before` take an object id and are mutually exclusive, and `has_more` says whether to continue. Page inside `execute_code`.\n- Search filters on a documented per-resource field set, not on arbitrary attributes. Charges search takes `amount`, `created`, `currency`, `customer`, `status`, `refunded`, `disputed`, `metadata`, `billing_details.address.postal_code`, and `payment_method_details.<source>.*` card fields — there is no `payment_intent` field. When the field you want is not searchable, retrieve the parent object and follow its reference (the PaymentIntent's `latest_charge`) instead of retrying the search with another spelling.\n- The account → `stripe_api_search` → `stripe_api_details` → `stripe_api_read` sequence is one program, not four turns: resolve the method once, then call that method as many times as the investigation needs in the same run.\n- Decline outcomes live on the charge — `outcome`, `failure_code`, `failure_message` — reached from the PaymentIntent's `latest_charge`, so \"why did this payment fail\" is a PaymentIntent read followed by one charge read.\n- Resolve ids before acting; never guess one. Stripe ids are typed prefixes — `cus_` customer, `sub_` subscription, `ch_` charge, `pi_` payment intent, `in_` invoice, `acct_` account — and a plausible-looking id belongs to a different object or to nobody.\n- Amounts are integers in the currency's minor unit: `1099` is 10.99 USD, and zero-decimal currencies like JPY take `10` for 10 JPY. Never send a decimal.\n- Treat every create, update, delete, refund, and report run as a write. For guest calls, classification, and routing, fetch `skills({ name: \"usage\" })`.\n",
4
4
  "instructionsHeading": "Account instructions",
5
5
  "fragments": {
6
- "guide_0": "\n\nThis OAuth session may expose both live and sandbox Stripe accounts. Discover the live account-selection tools, select the intended account and mode, then carry exact context fields such as `stripe_context` and `livemode` wherever the schema requires them. A live-mode write moves real money; a sandbox write changes test data. Never infer the account or mode from connector metadata.\n\n- Discover account and mode with the live account-selection tools before every account-scoped read or write. The published inventory has changed account-tool names; do not assume `list_available_accounts_or_orgs` is available. Select the intended result and carry the exact context fields required by the live schema unchanged. If the account, mode, or supported selector is ambiguous, stop and ask; never guess.\n- Organization accounts are not Stripe Connect connected accounts. A Connect call requires a separate connector with a deployment-configured restricted key plus Stripe's documented `Stripe-Account` header; OAuth does not support that path.\n",
7
- "guide_1": "\n\n- Organization accounts are not Stripe Connect connected accounts. A Connect call requires a deployment-configured restricted key plus Stripe's documented `Stripe-Account` header; OAuth does not support that path. Do not try to turn an organization-account call into a Connect call inside tool arguments.\n",
8
- "guide_2": "\n- Four generic tools reach any Stripe API method. Find the method with `stripe_api_search`, read its parameters with `stripe_api_details`, then call `stripe_api_read` (GET) or `stripe_api_write` (POST/PATCH/PUT/DELETE). Never guess a path or a parameter name — `stripe_api_details` is cheaper than a rejected write.\n- Prefer a dedicated tool when one covers the task: `get_balance_summary` for balances and `stripe_analytics` for Sigma or Metrics reporting. Use `stripe_api_search` for everything else instead of assuming a retired dedicated tool still exists.\n- `stripe_implementation_planner` and the query-execution intents of `stripe_analytics` create provider-side planning or query-run state. Connecta therefore classifies both as writes and enforces the configured pool trust policy; their retrieval paths stay behind the same tool boundary.\n- `stripe_api_write` carries the blast radius of the entire write API — every POST, PATCH, PUT, and DELETE, from a customer edit to a subscription cancellation. State the method and path explicitly; execution follows the configured pool trust policy.\n- Lists are cursor-paginated: `limit` defaults to 10 and caps at 100, `starting_after` and `ending_before` take an object id and are mutually exclusive, and `has_more` says whether to continue. Page inside `execute_code`.\n- Any `stripe_api_read` list or `stripe_api_search` that returns full objects belongs inside `execute_code`, projected to the fields the question needs before `return`. Neither `limit` nor `expand` substitutes for that: an unprojected list of customers or invoices truncates long before it answers, and a projected one keeps the customer's name, email, and address out of the transcript.\n- Search filters on a documented per-resource field set, not on arbitrary attributes. Charges search takes `amount`, `created`, `currency`, `customer`, `status`, `refunded`, `disputed`, `metadata`, `billing_details.address.postal_code`, and `payment_method_details.<source>.*` card fields — there is no `payment_intent` field. When the field you want is not searchable, retrieve the parent object and follow its reference (the PaymentIntent's `latest_charge`) instead of retrying the search with another spelling.\n- The account → `stripe_api_search` → `stripe_api_details` → `stripe_api_read` sequence is one program, not four turns: resolve the method once, then call that method as many times as the investigation needs in the same run.\n- Decline outcomes live on the charge — `outcome`, `failure_code`, `failure_message` — reached from the PaymentIntent's `latest_charge`, so \"why did this payment fail\" is a PaymentIntent read followed by one charge read.\n- Resolve ids before acting; never guess one. Stripe ids are typed prefixes — `cus_` customer, `sub_` subscription, `ch_` charge, `pi_` payment intent, `in_` invoice, `acct_` account — and a plausible-looking id belongs to a different object or to nobody. Find the object with `stripe_api_search` (or a list endpoint through `stripe_api_read`) and carry the `id` it returned into the write.\n- This connection's tool list is not a fixed set. Stripe gates parts of its MCP catalog by account, integration, and beta enrollment, so search this connector for what it actually exposes rather than assuming a documented tool is here.\n- Amounts are integers in the currency's minor unit: `1099` is 10.99 USD, and zero-decimal currencies like JPY take `10` for 10 JPY. Never send a decimal.\n- Send an `Idempotency-Key` on every write you might retry, if the tool accepts it, and reuse the same key for the retry. A retry with a fresh key is a second charge, not a second attempt.\n- Stripe answers a rate limit with `429` and a `Stripe-Rate-Limited-Reason` header; back off on that rather than retrying immediately. Stripe documents an account ceiling of ",
9
- "guide_3": ", and any single endpoint is capped at 25 per second regardless of mode, so paging one list is the real constraint.\n- Use `search_stripe_documentation` when the shape of an object or a flow is unclear; it is a read and costs nothing but a call.\n- Treat every create, update, delete, refund, and report run as a write. For guest calls, classification, and routing, fetch `skills({ name: \"usage\" })`.\n- An `auth_required` failure means this connector's Stripe authorization is missing or expired: run `authorize_connector` for this connector id, then retry the same call unchanged. A rejected argument or a plan restriction comes back in Stripe's own words instead — read it rather than re-authorizing.\n"
6
+ "oauth": "\n\nThis OAuth session may expose both live and sandbox Stripe accounts. Discover the live account-selection tools, select the intended account and mode, then carry exact context fields such as `stripe_context` and `livemode` wherever the schema requires them. A live-mode write moves real money; a sandbox write changes test data. Never infer the account or mode from connector metadata.\n\n- Discover account and mode with the live account-selection tools before every account-scoped read or write. The published inventory has changed account-tool names; do not assume `list_available_accounts_or_orgs` is available. Select the intended result and carry the exact context fields required by the live schema unchanged. If the account, mode, or supported selector is ambiguous, stop and ask; never guess.\n- Organization accounts are not Stripe Connect connected accounts. A Connect call requires a separate API-key connector whose `connectedAccount` sends Stripe's documented `Stripe-Account` header; OAuth does not support that path.\n- Four generic tools reach any Stripe API method. Find the method with `stripe_api_search`, read its parameters with `stripe_api_details`, then call `stripe_api_read` (GET) or `stripe_api_write` (POST/PATCH/PUT/DELETE). Never guess a path or a parameter name — `stripe_api_details` is cheaper than a rejected write.\n- Prefer a dedicated tool when one covers the task: `get_balance_summary` for balances and `stripe_analytics` for Sigma or Metrics reporting. Use `stripe_api_search` for everything else instead of assuming a retired dedicated tool still exists.\n- `stripe_implementation_planner` and the query-execution intents of `stripe_analytics` create provider-side planning or query-run state. Connecta therefore classifies both as writes and enforces the configured pool trust policy; their retrieval paths stay behind the same tool boundary.\n- `stripe_api_write` carries the blast radius of the entire write API — every POST, PATCH, PUT, and DELETE, from a customer edit to a subscription cancellation. State the method and path explicitly; execution follows the configured pool trust policy.\n- Any `stripe_api_read` list or `stripe_api_search` that returns full objects belongs inside `execute_code`, projected to the fields the question needs before `return`. Neither `limit` nor `expand` substitutes for that: an unprojected list of customers or invoices truncates long before it answers, and a projected one keeps the customer's name, email, and address out of the transcript.\n- Find an object with `stripe_api_search` (or a list endpoint through `stripe_api_read`) and carry the `id` it returned into the write.\n- This connection's tool list is not a fixed set. Stripe gates parts of its MCP catalog by account, integration, and beta enrollment, so search this connector for what it actually exposes rather than assuming a documented tool is here.\n- Send an `Idempotency-Key` on every write you might retry, if the tool accepts it, and reuse the same key for the retry. A retry with a fresh key is a second charge, not a second attempt.\n",
7
+ "key": "\n\n- This is Connecta's REST connector to `api.stripe.com`, holding one operator-managed key. It reaches exactly one account in one mode, so there is no account selection and no `stripe_context` or `livemode` argument.\n- `stripe_api_search` finds API operations, not records: it searches the pinned API index and names the tool that calls each operation. `stripe_api_details` returns one operation's parameters with types, enums, and required names. Neither sends a request.\n- `stripe_api_read` calls GET operations, plus `POST /v1/invoices/create_preview`, which only previews. `stripe_api_write` calls every other POST and every DELETE, from a customer edit to a subscription cancellation, with `method` and `path` stated; execution follows the configured pool trust policy.\n- Paths are concrete: substitute ids into the template search returned (`/v1/payment_intents/pi_123`). Pass `query` and `body` as JSON objects named as `stripe_api_details` lists them. Connecta form-encodes v1 bodies and query lists (`expand: [\"latest_charge\"]`), sends v2 bodies as JSON, and pins `Stripe-Version` to the index's API version. Send `null` or `\"\"` to unset a field.\n- Every call is checked against the pinned index before it is sent. An unknown path returns the nearest operations; an unknown or missing parameter returns `validation.issues` with the accepted names. Nothing reached Stripe, so fix the call from that answer.\n- Results arrive as `{ status, data, page? }`. On a list, `page.next` is the cursor and `page.param` the query parameter to pass it as: `starting_after`, or `page` for search and v2 lists. Lists belong inside `execute_code`; pass `select` (dot paths such as `data.id` and `data.status`) to keep only the fields the question needs, which also keeps customer names, emails, and addresses out of the transcript.\n- `get_stripe_account_info` confirms the account, mode, and connected account; `get_balance_summary` returns available and pending balances. Use them when they answer the question.\n- Find records with list and search operations through `stripe_api_read` (`GET /v1/customers/search`, `GET /v1/charges` with `customer`) and carry the returned `id` into the write.\n- `stripe_api_write` sends an `Idempotency-Key` on every POST. Pass `idempotencyKey` for anything you might retry, or reuse the generated one the result returns. An ambiguous failure carries it in its message or as `uncertainCall.recovery.idempotencyKey`. Reuse it only with the exact original arguments and within Stripe's retention (v1 keys may be pruned after 24 hours); after that, look the object up before retrying. A retry with a fresh key is a second charge, not a second attempt.\n- Refused by design: operations that hand out a credential (ephemeral keys, Terminal connection tokens, account links (v1 and v2), Terminal onboarding links, Express login links, account sessions, customer sessions, billing portal sessions, file link creation, Financial Connections session creation, and the meter event session), Stripe Apps secret payloads (`expand` of `payload`), Issuing card `number` and `cvc` expansion on any Issuing path, multipart uploads such as `POST /v1/files` (use the Dashboard), and the meter event stream (send single events with `POST /v2/billing/meter_events`). Each refusal names its reason. Quote PDFs arrive from `files.stripe.com` as base64.\n- Secrets read as `\"[redacted]\"` in every result, expansions included: client secrets (PaymentIntents, SetupIntents, Checkout and Identity sessions, Sources, Financial Connections, invoice confirmation secrets), webhook and event destination signing secrets, Apps secret payloads, app-install authorization codes, Terminal Wi-Fi passwords, forwarded request header values, and Issuing card numbers and CVCs. Webhook and event destination URLs, file link URLs (in an expanded File, a list, or an event too), and pre-signed import URLs keep only their scheme and host. Identity verification `url`s come back whole. A Checkout Session's `url` comes back whole only for a guest payment session (`mode: \"payment\"`, no `customer` or `customer_account`, no customer creation, no saved payment method options); any other session returns only its scheme and host, because its page can show, reuse, or remove a customer's saved payment methods. Create those from your own server code, or send a payment link or the invoice's hosted page instead. These rules follow each object wherever it appears, expansions and events included. A `[redacted]` field is never the real value; do not send it back.\n- Failures of credential-bearing operations (webhook endpoints, Apps secrets, Terminal configurations, forwarding) carry Stripe's type, code, decline code, and param, not its message. Payment, billing, and account failures keep Stripe's message.\n- The hosted OAuth connection's natural-language Sigma, metrics, documentation search, implementation planner, and feedback tools do not exist with a key.\n",
8
+ "shared": "- Lists are cursor-paginated: `limit` defaults to 10 and caps at 100, `starting_after` and `ending_before` take an object id and are mutually exclusive, and `has_more` says whether to continue. Page inside `execute_code`.\n- Search filters on a documented per-resource field set, not on arbitrary attributes. Charges search takes `amount`, `created`, `currency`, `customer`, `status`, `refunded`, `disputed`, `metadata`, `billing_details.address.postal_code`, and `payment_method_details.<source>.*` card fields — there is no `payment_intent` field. When the field you want is not searchable, retrieve the parent object and follow its reference (the PaymentIntent's `latest_charge`) instead of retrying the search with another spelling.\n- The account → `stripe_api_search` → `stripe_api_details` → `stripe_api_read` sequence is one program, not four turns: resolve the method once, then call that method as many times as the investigation needs in the same run.\n- Decline outcomes live on the charge — `outcome`, `failure_code`, `failure_message` — reached from the PaymentIntent's `latest_charge`, so \"why did this payment fail\" is a PaymentIntent read followed by one charge read.\n- Resolve ids before acting; never guess one. Stripe ids are typed prefixes — `cus_` customer, `sub_` subscription, `ch_` charge, `pi_` payment intent, `in_` invoice, `acct_` account — and a plausible-looking id belongs to a different object or to nobody.\n- Amounts are integers in the currency's minor unit: `1099` is 10.99 USD, and zero-decimal currencies like JPY take `10` for 10 JPY. Never send a decimal.\n- Treat every create, update, delete, refund, and report run as a write. For guest calls, classification, and routing, fetch `skills({ name: \"usage\" })`.\n",
9
+ "oauth_limits": "- Stripe answers a rate limit with `429` and a `Stripe-Rate-Limited-Reason` header; back off on that rather than retrying immediately. Stripe documents an account ceiling of 100 requests per second in live mode and 25 in sandbox mode, and any single endpoint is capped at 25 per second regardless of mode, so paging one list is the real constraint.\n- Use `search_stripe_documentation` when the shape of an object or a flow is unclear; it is a read and costs nothing but a call.\n- An `auth_required` failure means this connector's Stripe authorization is missing or expired: run `authorize_connector` for this connector id, then retry the same call unchanged. A rejected argument or a plan restriction comes back in Stripe's own words instead — read it rather than re-authorizing.\n",
10
+ "key_limits": "- Stripe answers a rate limit with `429` and a `Stripe-Rate-Limited-Reason` header; back off on that rather than retrying immediately. Stripe documents an account ceiling of ",
11
+ "key_tail": ", and any single endpoint is capped at 25 per second regardless of mode, so paging one list is the real constraint. A `429` with code `lock_timeout` is a lock conflict on one object, not a rate limit: Stripe did not process the request, so it fails as `conflict` and may be retried after the other request finishes.\n- An `auth_required` failure means the key is missing, revoked, or for the other mode: an operator must replace it in this connection in the operator UI, and `authorize_connector` returns that handoff. A restricted key without a permission fails as `provider_permission_denied` naming it; ask for the permission rather than retrying. A rejected argument comes back in Stripe's own words — read it rather than replacing the key.\n"
10
12
  }
11
13
  };