@usethrottle/mcp 0.2.0 → 0.2.2
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/README.md +9 -2
- package/dist/contract-index.json +1 -1
- package/dist/docs-index.json +1 -1
- package/dist/lib.cjs +1784 -153
- package/dist/lib.cjs.map +1 -1
- package/dist/lib.d.cts +58 -4
- package/dist/lib.d.ts +58 -4
- package/dist/lib.js +1784 -153
- package/dist/lib.js.map +1 -1
- package/dist/mcp.js +1784 -153
- package/dist/mcp.js.map +1 -1
- package/package.json +3 -3
package/dist/lib.cjs
CHANGED
|
@@ -32,6 +32,7 @@ module.exports = __toCommonJS(lib_exports);
|
|
|
32
32
|
|
|
33
33
|
// src/server.ts
|
|
34
34
|
var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
35
|
+
var import_types = require("@modelcontextprotocol/sdk/types.js");
|
|
35
36
|
|
|
36
37
|
// ../core-scopes/src/scopes.ts
|
|
37
38
|
function hasScope(granted, required) {
|
|
@@ -60,6 +61,8 @@ function add(group, resource, actions, labels, opts = {}) {
|
|
|
60
61
|
staffOnly: opts.staffOnly ?? false,
|
|
61
62
|
// publishable defaults false; only the curated set below is true
|
|
62
63
|
publishableAllowed: opts.publishableAllowed ?? false,
|
|
64
|
+
// publishableOnly defaults false; only customer_accounts:write sets it
|
|
65
|
+
publishableOnly: opts.publishableOnly ?? false,
|
|
63
66
|
// extension default true for commerce scopes; Taxonomy B passes false
|
|
64
67
|
extensionAllowed: opts.extensionAllowed ?? true,
|
|
65
68
|
implies
|
|
@@ -70,19 +73,36 @@ add("Orders", "orders", ["read", "write"], { read: "View orders", write: "Manage
|
|
|
70
73
|
add("Quotes", "quotes", ["read", "write"], { read: "View quotes", write: "Manage quotes" });
|
|
71
74
|
add("Orders", "order_cancellations", ["write"], { write: "Cancel orders" });
|
|
72
75
|
add("Orders", "order_returns", ["read", "write"], { read: "View returns", write: "Process returns" });
|
|
76
|
+
add("Orders", "order_comps", ["write"], { write: "Comp orders (write off the balance)" }, { sensitive: true });
|
|
77
|
+
add("Orders", "order_holds", ["write"], { write: "Freeze and unfreeze orders" });
|
|
78
|
+
add(
|
|
79
|
+
"Imports",
|
|
80
|
+
"imports",
|
|
81
|
+
["read", "write"],
|
|
82
|
+
{ read: "View data imports", write: "Run and undo data imports" },
|
|
83
|
+
{ extensionAllowed: false }
|
|
84
|
+
);
|
|
73
85
|
add("Payments", "payments", ["read", "write"], { read: "View payments", write: "Create payments" });
|
|
74
86
|
add("Payments", "payment_authorizations", ["write"], { write: "Authorize payments" });
|
|
75
87
|
add("Payments", "payment_captures", ["write"], { write: "Capture payments" });
|
|
76
88
|
add("Payments", "payment_refunds", ["read", "write"], { read: "View refunds", write: "Refund payments" }, { sensitive: true });
|
|
77
89
|
add("Payments", "payment_voids", ["write"], { write: "Void payments" }, { sensitive: true });
|
|
90
|
+
add("Payments", "payment_records", ["write"], { write: "Record payments received outside Throttle" }, { sensitive: true });
|
|
78
91
|
add("Payments", "payment_disputes", ["read", "write"], { read: "View disputes", write: "Handle disputes" }, { sensitive: true });
|
|
79
92
|
add("Payments", "payment_methods", ["read", "write"], { read: "View payment methods", write: "Tokenize payment methods" }, { sensitive: true });
|
|
80
93
|
add("Customers", "customers", ["read", "write"], { read: "View customers", write: "Manage customers" });
|
|
81
94
|
add("Customers", "customer_pii", ["read"], { read: "View customer PII" }, { sensitive: true });
|
|
82
95
|
add("Customers", "customer_addresses", ["read", "write"], { read: "View addresses", write: "Manage addresses" });
|
|
83
96
|
add("Customers", "customer_payment_methods", ["read", "write"], { read: "View stored cards", write: "Manage stored cards" }, { sensitive: true });
|
|
97
|
+
add(
|
|
98
|
+
"Customer accounts",
|
|
99
|
+
"customer_accounts",
|
|
100
|
+
["write"],
|
|
101
|
+
{ write: "Let shoppers register, sign in and manage their account" },
|
|
102
|
+
{ publishableAllowed: true, publishableOnly: true, extensionAllowed: false }
|
|
103
|
+
);
|
|
84
104
|
add("Carts", "carts", ["read", "write"], { read: "View carts", write: "Manage carts" });
|
|
85
|
-
add("Carts", "cart_items", ["
|
|
105
|
+
add("Carts", "cart_items", ["write"], { write: "Manage line items" });
|
|
86
106
|
add("Carts", "checkout_sessions", ["read", "write"], { read: "View checkout sessions", write: "Manage checkout sessions" });
|
|
87
107
|
add("Discounts", "discounts", ["read", "write"], { read: "View discounts", write: "Manage discounts" });
|
|
88
108
|
add("Subscriptions", "subscriptions", ["read", "write"], { read: "View subscriptions", write: "Manage subscriptions" });
|
|
@@ -103,8 +123,32 @@ add("Shipping & Tax", "shipping_quotes", ["write"], { write: "Request shipping q
|
|
|
103
123
|
add("Shipping & Tax", "tax_calculations", ["write"], { write: "Calculate tax" }, { publishableAllowed: true });
|
|
104
124
|
add("Shipping & Tax", "shipping_tax_quote_tokens", ["read", "write"], { read: "View quote tokens", write: "Manage quote tokens" });
|
|
105
125
|
add("Shipping & Tax", "shipping_tax_snapshots", ["write"], { write: "Accept external snapshots" });
|
|
126
|
+
add(
|
|
127
|
+
"Scripts",
|
|
128
|
+
"storefront_scripts",
|
|
129
|
+
["read", "write"],
|
|
130
|
+
{
|
|
131
|
+
read: "Load storefront scripts",
|
|
132
|
+
// `write` covers exactly one thing: reporting a script's own load outcome
|
|
133
|
+
// from the merchant's storefront, so the health panel stops being
|
|
134
|
+
// hosted-checkout-only. On checkout the session id in the URL is the
|
|
135
|
+
// capability that scopes that write; a storefront page has no session, so
|
|
136
|
+
// the pk it already holds is what scopes it instead. It writes nothing but
|
|
137
|
+
// load telemetry — authoring stays on `application_scripts:write`, which
|
|
138
|
+
// is not publishable-allowed.
|
|
139
|
+
write: "Report storefront script load outcomes"
|
|
140
|
+
},
|
|
141
|
+
{ publishableAllowed: true }
|
|
142
|
+
);
|
|
143
|
+
add("Scripts", "application_scripts", ["read", "write"], {
|
|
144
|
+
read: "View merchant-authored scripts",
|
|
145
|
+
write: "Create, edit and disable merchant-authored scripts"
|
|
146
|
+
}, { extensionAllowed: false });
|
|
147
|
+
add("Scripts", "extension_scripts", ["read", "write"], {
|
|
148
|
+
read: "View an extension's buyer-facing scripts",
|
|
149
|
+
write: "Create, edit and delete an extension's buyer-facing scripts"
|
|
150
|
+
}, { extensionAllowed: false });
|
|
106
151
|
add("Connectors", "connectors", ["read", "write"], { read: "View connectors", write: "Manage connectors" });
|
|
107
|
-
add("Connectors", "connector_activations", ["write"], { write: "Activate/deactivate connectors" });
|
|
108
152
|
add("Webhooks & Events", "webhooks", ["read", "write"], { read: "View webhooks", write: "Manage webhooks" });
|
|
109
153
|
add("Webhooks & Events", "webhook_deliveries", ["read", "write"], { read: "View deliveries", write: "Retry deliveries" });
|
|
110
154
|
add("Webhooks & Events", "events", ["read"], { read: "View events" });
|
|
@@ -120,7 +164,7 @@ add("Platform billing", "billing", ["read", "write"], { read: "View billing", wr
|
|
|
120
164
|
add("Platform billing", "billing_payment_methods", ["read", "write"], { read: "View billing cards", write: "Manage billing cards" }, HUMAN);
|
|
121
165
|
add("Email", "email_templates", ["read", "write"], { read: "View email templates", write: "Edit email templates" }, HUMAN);
|
|
122
166
|
add("Email", "email_settings", ["read", "write"], { read: "View email settings", write: "Edit email settings" }, HUMAN);
|
|
123
|
-
add("Go-live", "go_live", ["
|
|
167
|
+
add("Go-live", "go_live", ["write"], { write: "Submit go-live" }, HUMAN);
|
|
124
168
|
add("Partner", "partner", ["read", "write"], { read: "View partner profile", write: "Edit partner profile" }, HUMAN);
|
|
125
169
|
add("Partner", "affiliates", ["read", "write"], { read: "View affiliates", write: "Manage affiliates" }, HUMAN);
|
|
126
170
|
add("Notifications", "notifications", ["write"], { write: "Send notifications" }, HUMAN);
|
|
@@ -144,6 +188,7 @@ REG["extensions:install"] = {
|
|
|
144
188
|
sensitive: false,
|
|
145
189
|
staffOnly: false,
|
|
146
190
|
publishableAllowed: false,
|
|
191
|
+
publishableOnly: false,
|
|
147
192
|
extensionAllowed: false,
|
|
148
193
|
implies: []
|
|
149
194
|
};
|
|
@@ -156,6 +201,7 @@ REG["*"] = {
|
|
|
156
201
|
sensitive: true,
|
|
157
202
|
staffOnly: false,
|
|
158
203
|
publishableAllowed: false,
|
|
204
|
+
publishableOnly: false,
|
|
159
205
|
extensionAllowed: false,
|
|
160
206
|
implies: []
|
|
161
207
|
};
|
|
@@ -175,6 +221,9 @@ var EVENT_SCOPE_OVERRIDES = {
|
|
|
175
221
|
"payment.refunded": "payment_refunds:read",
|
|
176
222
|
"payment.partially_refunded": "payment_refunds:read",
|
|
177
223
|
"payment.refund_failed": "payment_refunds:read",
|
|
224
|
+
// Money returned, so it belongs with the other refund events rather than
|
|
225
|
+
// behind the broad subscriptions:read the `subscription.` prefix would give.
|
|
226
|
+
"subscription.invoice_refunded": "payment_refunds:read",
|
|
178
227
|
"payment.disputed": "payment_disputes:read",
|
|
179
228
|
"payment.dispute_cleared": "payment_disputes:read",
|
|
180
229
|
"fulfillment.digital.delivered": "fulfillment_digital:read"
|
|
@@ -191,9 +240,17 @@ var PREFIX_RULES = [
|
|
|
191
240
|
["fulfillment.", "fulfillments:read"],
|
|
192
241
|
["discount.", "discounts:read"],
|
|
193
242
|
["invoice.", "invoices:read"],
|
|
243
|
+
["import.", "imports:read"],
|
|
194
244
|
["shipping_tax.", "shipping_tax:read"],
|
|
195
245
|
["workspace.", "billing:read"],
|
|
196
|
-
|
|
246
|
+
// `extensions:read` is a human scope (extensions must never watch other
|
|
247
|
+
// extensions), so only a workspace endpoint can SUBSCRIBE to this family.
|
|
248
|
+
// The uninstalled installation's own endpoint is addressed directly by the
|
|
249
|
+
// uninstall path and needs no subscription at all.
|
|
250
|
+
["extension.", "extensions:read"],
|
|
251
|
+
// Subscribing to script outcomes needs script read — the payload names the
|
|
252
|
+
// script key and its hash, which is the merchant's script inventory.
|
|
253
|
+
["script.", "application_scripts:read"]
|
|
197
254
|
];
|
|
198
255
|
function eventReadScope(type) {
|
|
199
256
|
if (EVENT_SCOPE_OVERRIDES[type]) return EVENT_SCOPE_OVERRIDES[type];
|
|
@@ -205,8 +262,90 @@ function eventReadScope(type) {
|
|
|
205
262
|
|
|
206
263
|
// ../core-scopes/src/issuance.ts
|
|
207
264
|
var PUBLISHABLE_SCOPES = Object.values(SCOPE_REGISTRY).filter((d) => d.publishableAllowed).map((d) => d.id);
|
|
265
|
+
var CONNECTOR_GRANTABLE_SENSITIVE_SCOPES = ["customer_pii:read"];
|
|
266
|
+
function validateScopesForOAuth(scopes, opts = {}) {
|
|
267
|
+
const invalid = [];
|
|
268
|
+
for (const id of scopes) {
|
|
269
|
+
if (id === "*") {
|
|
270
|
+
invalid.push(id);
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
const def = SCOPE_REGISTRY[id];
|
|
274
|
+
if (!def || def.staffOnly) {
|
|
275
|
+
invalid.push(id);
|
|
276
|
+
continue;
|
|
277
|
+
}
|
|
278
|
+
if (def.sensitive && !(opts.connector && CONNECTOR_GRANTABLE_SENSITIVE_SCOPES.includes(id))) invalid.push(id);
|
|
279
|
+
}
|
|
280
|
+
return { ok: invalid.length === 0, invalid };
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// ../core-scopes/src/shopper-pii.ts
|
|
284
|
+
var PERSON_KEYS = /* @__PURE__ */ new Set([
|
|
285
|
+
"email",
|
|
286
|
+
"customerEmail",
|
|
287
|
+
"billingEmail",
|
|
288
|
+
"buyerEmail",
|
|
289
|
+
"authorEmail",
|
|
290
|
+
"authorName",
|
|
291
|
+
"phone",
|
|
292
|
+
"firstName",
|
|
293
|
+
"lastName",
|
|
294
|
+
"addressLine1",
|
|
295
|
+
"addressLine2",
|
|
296
|
+
"billingAddressLines",
|
|
297
|
+
"processorResponse",
|
|
298
|
+
"shareToken",
|
|
299
|
+
"company",
|
|
300
|
+
"externalId",
|
|
301
|
+
"externalCustomerId",
|
|
302
|
+
// Free-form: contents cannot be inspected for a person, so the field goes.
|
|
303
|
+
"metadata",
|
|
304
|
+
"notes",
|
|
305
|
+
"internalNotes",
|
|
306
|
+
"buyerNote",
|
|
307
|
+
"buyerReference",
|
|
308
|
+
"changeSummary",
|
|
309
|
+
"declinedReason",
|
|
310
|
+
"compReason",
|
|
311
|
+
"holdReason",
|
|
312
|
+
"body",
|
|
313
|
+
"acceptance",
|
|
314
|
+
"acceptanceSelections",
|
|
315
|
+
"clientContext",
|
|
316
|
+
"ipAddress",
|
|
317
|
+
"userAgent",
|
|
318
|
+
"vatNumber",
|
|
319
|
+
"buyerVatNumber",
|
|
320
|
+
"taxExemptionNumber"
|
|
321
|
+
]);
|
|
322
|
+
var PERSON_CONTAINERS = /* @__PURE__ */ new Set(["customer", "buyer"]);
|
|
323
|
+
function isPlainObject(value) {
|
|
324
|
+
if (value === null || typeof value !== "object") return false;
|
|
325
|
+
const proto = Object.getPrototypeOf(value);
|
|
326
|
+
return proto === Object.prototype || proto === null;
|
|
327
|
+
}
|
|
328
|
+
function walk(value, parentKey, depth) {
|
|
329
|
+
if (depth > 32) return value;
|
|
330
|
+
if (Array.isArray(value)) return value.map((item) => walk(item, parentKey, depth + 1));
|
|
331
|
+
if (!isPlainObject(value)) return value;
|
|
332
|
+
const out = {};
|
|
333
|
+
const isPerson = parentKey !== null && PERSON_CONTAINERS.has(parentKey);
|
|
334
|
+
for (const [key, child] of Object.entries(value)) {
|
|
335
|
+
if (PERSON_KEYS.has(key) || isPerson && key === "name") {
|
|
336
|
+
out[key] = child === void 0 ? child : null;
|
|
337
|
+
} else {
|
|
338
|
+
out[key] = walk(child, key, depth + 1);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
return out;
|
|
342
|
+
}
|
|
343
|
+
function redactShopperPii(value) {
|
|
344
|
+
return walk(value, null, 0);
|
|
345
|
+
}
|
|
208
346
|
|
|
209
347
|
// ../action-catalog/src/actions.ts
|
|
348
|
+
var OAUTH_WITHHELD = "Over an OAuth connection (ChatGPT, Claude) these are withheld, because a buyer can type them: line-item and quote-item names, SKUs and reference ids; company; every address line except the country; the merchant's external ids; notes, messages, reasons and metadata. Describe items by quantity and amount, and say the rest is withheld rather than missing.";
|
|
210
349
|
var ACTIONS = [
|
|
211
350
|
// ---------------------------------------------------------------- identity
|
|
212
351
|
{
|
|
@@ -223,7 +362,7 @@ var ACTIONS = [
|
|
|
223
362
|
{
|
|
224
363
|
id: "search_docs",
|
|
225
364
|
title: "Search the Throttle developer docs",
|
|
226
|
-
description: "Full-text search over the Throttle developer documentation. Returns titles, canonical
|
|
365
|
+
description: "Full-text search over the Throttle developer documentation. Returns titles, canonical usethrottle.dev/docs URLs, and matching excerpts. Prefer this over recalling API details from memory.",
|
|
227
366
|
kind: "read",
|
|
228
367
|
scopes: [],
|
|
229
368
|
input: [
|
|
@@ -260,7 +399,7 @@ var ACTIONS = [
|
|
|
260
399
|
{
|
|
261
400
|
id: "validate_payload",
|
|
262
401
|
title: "Validate a request body against the API contract",
|
|
263
|
-
description: "Check a request body against the published contract before sending it. Names every unsupported field and gives its canonical replacement \u2014 Throttle is strict camelCase and rejects retired aliases such as line_items, unit_price, customer_id and storeId.",
|
|
402
|
+
description: "Check a request body against the published contract before sending it. Names every unsupported field and gives its canonical replacement \u2014 Throttle is strict camelCase and rejects retired aliases such as line_items, unit_price, customer_id and storeId. Full reference: https://usethrottle.dev/docs/developers/api-reference.",
|
|
264
403
|
kind: "read",
|
|
265
404
|
scopes: [],
|
|
266
405
|
input: [
|
|
@@ -275,15 +414,22 @@ var ACTIONS = [
|
|
|
275
414
|
{
|
|
276
415
|
id: "api_get",
|
|
277
416
|
title: "Read any Throttle API endpoint",
|
|
278
|
-
description: "Escape hatch for reads the curated tools do not cover: performs a GET against any /api/v1 path and returns the response. Cannot write. Confirm the path with describe_endpoint first if unsure.",
|
|
417
|
+
description: "Escape hatch for reads the curated tools do not cover: performs a GET against any /api/v1 path and returns the response. Cannot write. Confirm the path with describe_endpoint first if unsure. Full reference: https://usethrottle.dev/docs/developers/api-reference.",
|
|
279
418
|
kind: "read",
|
|
280
419
|
scopes: [],
|
|
420
|
+
credentialScoped: true,
|
|
281
421
|
input: [
|
|
282
422
|
{
|
|
283
423
|
name: "path",
|
|
284
424
|
type: "string",
|
|
285
425
|
required: true,
|
|
286
426
|
description: "Path beginning with /api/v1, query string included."
|
|
427
|
+
},
|
|
428
|
+
{
|
|
429
|
+
name: "applicationId",
|
|
430
|
+
type: "string",
|
|
431
|
+
required: false,
|
|
432
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
287
433
|
}
|
|
288
434
|
],
|
|
289
435
|
requests: [],
|
|
@@ -305,11 +451,33 @@ var ACTIONS = [
|
|
|
305
451
|
{ name: "q", type: "string", required: false, description: "Free-text search." },
|
|
306
452
|
{ name: "status", type: "string", required: false, description: "Order status filter." },
|
|
307
453
|
{ name: "paymentStatus", type: "string", required: false, description: "Payment status filter." },
|
|
454
|
+
{
|
|
455
|
+
name: "createdAfter",
|
|
456
|
+
type: "string",
|
|
457
|
+
required: false,
|
|
458
|
+
description: "ISO 8601 lower bound on creation time, exclusive. For a single day, pass that day's midnight and the next day's midnight."
|
|
459
|
+
},
|
|
460
|
+
{
|
|
461
|
+
name: "createdBefore",
|
|
462
|
+
type: "string",
|
|
463
|
+
required: false,
|
|
464
|
+
description: "ISO 8601 upper bound on creation time, exclusive."
|
|
465
|
+
},
|
|
308
466
|
{ name: "limit", type: "number", required: false, description: "Page size." },
|
|
309
|
-
{ name: "cursor", type: "string", required: false, description: "Pagination cursor." }
|
|
467
|
+
{ name: "cursor", type: "string", required: false, description: "Pagination cursor." },
|
|
468
|
+
{
|
|
469
|
+
name: "applicationId",
|
|
470
|
+
type: "string",
|
|
471
|
+
required: false,
|
|
472
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
473
|
+
}
|
|
310
474
|
],
|
|
311
475
|
requests: [{ method: "GET", path: "/api/v1/orders" }],
|
|
312
|
-
channels: ["mcp", "zapier", "make", "n8n"]
|
|
476
|
+
channels: ["mcp", "zapier", "make", "n8n"],
|
|
477
|
+
guardrails: [
|
|
478
|
+
OAUTH_WITHHELD,
|
|
479
|
+
"Over an OAuth connection, q matches the order id, order number and status fields only."
|
|
480
|
+
]
|
|
313
481
|
},
|
|
314
482
|
{
|
|
315
483
|
id: "get_order",
|
|
@@ -317,7 +485,16 @@ var ACTIONS = [
|
|
|
317
485
|
description: "Full picture of a single order: the order itself plus its payments, returns and fulfillments in one response.",
|
|
318
486
|
kind: "read",
|
|
319
487
|
scopes: ["orders:read"],
|
|
320
|
-
|
|
488
|
+
sectionScopes: ["payments:read", "order_returns:read", "fulfillments:read"],
|
|
489
|
+
input: [
|
|
490
|
+
{ name: "id", type: "string", required: true, description: "Order id." },
|
|
491
|
+
{
|
|
492
|
+
name: "applicationId",
|
|
493
|
+
type: "string",
|
|
494
|
+
required: false,
|
|
495
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
496
|
+
}
|
|
497
|
+
],
|
|
321
498
|
requests: [
|
|
322
499
|
{ method: "GET", path: "/api/v1/orders/:id" },
|
|
323
500
|
{ method: "GET", path: "/api/v1/orders/:id/payments" },
|
|
@@ -326,23 +503,45 @@ var ACTIONS = [
|
|
|
326
503
|
],
|
|
327
504
|
channels: ["mcp"],
|
|
328
505
|
guardrails: [
|
|
329
|
-
"Sub-reads need their own scopes (payments:read, order_returns:read, fulfillments:read). Skip a section the credential cannot read and say so in the result rather than failing the whole call."
|
|
506
|
+
"Sub-reads need their own scopes (payments:read, order_returns:read, fulfillments:read). Skip a section the credential cannot read and say so in the result rather than failing the whole call.",
|
|
507
|
+
OAUTH_WITHHELD
|
|
330
508
|
]
|
|
331
509
|
},
|
|
332
510
|
// ---------------------------------------------------------------- customers
|
|
333
511
|
{
|
|
334
512
|
id: "search_customers",
|
|
335
513
|
title: "Search customers",
|
|
336
|
-
description: "Find customers by name, email or external id.",
|
|
514
|
+
description: "Find customers by name, email or external id, or list them by creation date. Over an OAuth connection (ChatGPT, Claude) text search is not available \u2014 list by date instead \u2014 because every field it could match is withheld there.",
|
|
337
515
|
kind: "read",
|
|
338
516
|
scopes: ["customers:read"],
|
|
339
517
|
input: [
|
|
340
518
|
{ name: "q", type: "string", required: false, description: "Free-text search." },
|
|
519
|
+
{
|
|
520
|
+
name: "createdAfter",
|
|
521
|
+
type: "string",
|
|
522
|
+
required: false,
|
|
523
|
+
description: "ISO 8601 lower bound on creation time, exclusive. For a single day, pass that day's midnight and the next day's midnight."
|
|
524
|
+
},
|
|
525
|
+
{
|
|
526
|
+
name: "createdBefore",
|
|
527
|
+
type: "string",
|
|
528
|
+
required: false,
|
|
529
|
+
description: "ISO 8601 upper bound on creation time, exclusive."
|
|
530
|
+
},
|
|
341
531
|
{ name: "limit", type: "number", required: false, description: "Page size." },
|
|
342
|
-
{ name: "cursor", type: "string", required: false, description: "Pagination cursor." }
|
|
532
|
+
{ name: "cursor", type: "string", required: false, description: "Pagination cursor." },
|
|
533
|
+
{
|
|
534
|
+
name: "applicationId",
|
|
535
|
+
type: "string",
|
|
536
|
+
required: false,
|
|
537
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
538
|
+
}
|
|
343
539
|
],
|
|
344
540
|
requests: [{ method: "GET", path: "/api/v1/customers" }],
|
|
345
|
-
channels: ["mcp", "zapier", "make", "n8n"]
|
|
541
|
+
channels: ["mcp", "zapier", "make", "n8n"],
|
|
542
|
+
guardrails: [
|
|
543
|
+
'email, phone, firstName, lastName, name and metadata are withheld (null, or absent from the result) unless the credential holds customer_pii:read, which OAuth connections cannot hold. Over an OAuth connection company, externalId and externalCustomerId are withheld too, and q is refused. Do not describe a missing name/email as "no name on file"; say it is withheld.'
|
|
544
|
+
]
|
|
346
545
|
},
|
|
347
546
|
{
|
|
348
547
|
id: "get_customer",
|
|
@@ -350,7 +549,16 @@ var ACTIONS = [
|
|
|
350
549
|
description: "Full picture of a single customer: the record plus their subscriptions and addresses in one response.",
|
|
351
550
|
kind: "read",
|
|
352
551
|
scopes: ["customers:read"],
|
|
353
|
-
|
|
552
|
+
sectionScopes: ["customer_addresses:read", "subscriptions:read"],
|
|
553
|
+
input: [
|
|
554
|
+
{ name: "id", type: "string", required: true, description: "Customer id." },
|
|
555
|
+
{
|
|
556
|
+
name: "applicationId",
|
|
557
|
+
type: "string",
|
|
558
|
+
required: false,
|
|
559
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
560
|
+
}
|
|
561
|
+
],
|
|
354
562
|
requests: [
|
|
355
563
|
{ method: "GET", path: "/api/v1/customers/:id" },
|
|
356
564
|
{ method: "GET", path: "/api/v1/customers/:id/addresses" },
|
|
@@ -358,7 +566,9 @@ var ACTIONS = [
|
|
|
358
566
|
],
|
|
359
567
|
channels: ["mcp"],
|
|
360
568
|
guardrails: [
|
|
361
|
-
"Sub-reads need customer_addresses:read / subscriptions:read. Omit a section the credential cannot read instead of failing."
|
|
569
|
+
"Sub-reads need customer_addresses:read / subscriptions:read. Omit a section the credential cannot read instead of failing.",
|
|
570
|
+
"email, phone, firstName, lastName, name and metadata on the customer record are withheld (null, or absent from the result) unless the credential holds customer_pii:read, which OAuth connections cannot hold. Say PII is redacted rather than reporting the buyer as anonymous.",
|
|
571
|
+
OAUTH_WITHHELD
|
|
362
572
|
]
|
|
363
573
|
},
|
|
364
574
|
// ------------------------------------------------------------- subscriptions
|
|
@@ -371,8 +581,26 @@ var ACTIONS = [
|
|
|
371
581
|
input: [
|
|
372
582
|
{ name: "customerId", type: "string", required: false, description: "Filter by customer." },
|
|
373
583
|
{ name: "status", type: "string", required: false, description: "Filter by status." },
|
|
584
|
+
{
|
|
585
|
+
name: "createdAfter",
|
|
586
|
+
type: "string",
|
|
587
|
+
required: false,
|
|
588
|
+
description: "ISO 8601 lower bound on creation time, exclusive. For a single day, pass that day's midnight and the next day's midnight."
|
|
589
|
+
},
|
|
590
|
+
{
|
|
591
|
+
name: "createdBefore",
|
|
592
|
+
type: "string",
|
|
593
|
+
required: false,
|
|
594
|
+
description: "ISO 8601 upper bound on creation time, exclusive."
|
|
595
|
+
},
|
|
374
596
|
{ name: "limit", type: "number", required: false, description: "Page size." },
|
|
375
|
-
{ name: "cursor", type: "string", required: false, description: "Pagination cursor." }
|
|
597
|
+
{ name: "cursor", type: "string", required: false, description: "Pagination cursor." },
|
|
598
|
+
{
|
|
599
|
+
name: "applicationId",
|
|
600
|
+
type: "string",
|
|
601
|
+
required: false,
|
|
602
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
603
|
+
}
|
|
376
604
|
],
|
|
377
605
|
requests: [{ method: "GET", path: "/api/v1/subscriptions" }],
|
|
378
606
|
channels: ["mcp", "zapier", "make", "n8n"]
|
|
@@ -383,7 +611,15 @@ var ACTIONS = [
|
|
|
383
611
|
description: "Read a single subscription including its current period and pending changes.",
|
|
384
612
|
kind: "read",
|
|
385
613
|
scopes: ["subscriptions:read"],
|
|
386
|
-
input: [
|
|
614
|
+
input: [
|
|
615
|
+
{ name: "id", type: "string", required: true, description: "Subscription id." },
|
|
616
|
+
{
|
|
617
|
+
name: "applicationId",
|
|
618
|
+
type: "string",
|
|
619
|
+
required: false,
|
|
620
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
621
|
+
}
|
|
622
|
+
],
|
|
387
623
|
requests: [{ method: "GET", path: "/api/v1/subscriptions/:id" }],
|
|
388
624
|
channels: ["mcp", "zapier", "make", "n8n"]
|
|
389
625
|
},
|
|
@@ -396,8 +632,26 @@ var ACTIONS = [
|
|
|
396
632
|
scopes: ["quotes:read"],
|
|
397
633
|
input: [
|
|
398
634
|
{ name: "status", type: "string", required: false, description: "Filter by quote status." },
|
|
635
|
+
{
|
|
636
|
+
name: "createdAfter",
|
|
637
|
+
type: "string",
|
|
638
|
+
required: false,
|
|
639
|
+
description: "ISO 8601 lower bound on creation time, exclusive. For a single day, pass that day's midnight and the next day's midnight."
|
|
640
|
+
},
|
|
641
|
+
{
|
|
642
|
+
name: "createdBefore",
|
|
643
|
+
type: "string",
|
|
644
|
+
required: false,
|
|
645
|
+
description: "ISO 8601 upper bound on creation time, exclusive."
|
|
646
|
+
},
|
|
399
647
|
{ name: "limit", type: "number", required: false, description: "Page size." },
|
|
400
|
-
{ name: "cursor", type: "string", required: false, description: "Pagination cursor." }
|
|
648
|
+
{ name: "cursor", type: "string", required: false, description: "Pagination cursor." },
|
|
649
|
+
{
|
|
650
|
+
name: "applicationId",
|
|
651
|
+
type: "string",
|
|
652
|
+
required: false,
|
|
653
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
654
|
+
}
|
|
401
655
|
],
|
|
402
656
|
requests: [{ method: "GET", path: "/api/v1/quotes" }],
|
|
403
657
|
channels: ["mcp", "zapier", "make", "n8n"]
|
|
@@ -405,12 +659,21 @@ var ACTIONS = [
|
|
|
405
659
|
{
|
|
406
660
|
id: "get_quote",
|
|
407
661
|
title: "Get one quote",
|
|
408
|
-
description: "Read a single quote with its line items
|
|
662
|
+
description: "Read a single quote: status, totals, and each revision with its line items and payment terms.",
|
|
409
663
|
kind: "read",
|
|
410
664
|
scopes: ["quotes:read"],
|
|
411
|
-
input: [
|
|
665
|
+
input: [
|
|
666
|
+
{ name: "id", type: "string", required: true, description: "Quote id." },
|
|
667
|
+
{
|
|
668
|
+
name: "applicationId",
|
|
669
|
+
type: "string",
|
|
670
|
+
required: false,
|
|
671
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
672
|
+
}
|
|
673
|
+
],
|
|
412
674
|
requests: [{ method: "GET", path: "/api/v1/quotes/:id" }],
|
|
413
|
-
channels: ["mcp", "zapier", "make", "n8n"]
|
|
675
|
+
channels: ["mcp", "zapier", "make", "n8n"],
|
|
676
|
+
guardrails: [OAUTH_WITHHELD]
|
|
414
677
|
},
|
|
415
678
|
// ----------------------------------------------------------------- invoices
|
|
416
679
|
{
|
|
@@ -422,10 +685,17 @@ var ACTIONS = [
|
|
|
422
685
|
input: [
|
|
423
686
|
{ name: "status", type: "string", required: false, description: "Filter by invoice status." },
|
|
424
687
|
{ name: "limit", type: "number", required: false, description: "Page size." },
|
|
425
|
-
{ name: "cursor", type: "string", required: false, description: "Pagination cursor." }
|
|
688
|
+
{ name: "cursor", type: "string", required: false, description: "Pagination cursor." },
|
|
689
|
+
{
|
|
690
|
+
name: "applicationId",
|
|
691
|
+
type: "string",
|
|
692
|
+
required: false,
|
|
693
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
694
|
+
}
|
|
426
695
|
],
|
|
427
696
|
requests: [{ method: "GET", path: "/api/v1/invoices" }],
|
|
428
|
-
channels: ["mcp", "zapier", "make", "n8n"]
|
|
697
|
+
channels: ["mcp", "zapier", "make", "n8n"],
|
|
698
|
+
guardrails: [OAUTH_WITHHELD]
|
|
429
699
|
},
|
|
430
700
|
{
|
|
431
701
|
id: "receivables_aging",
|
|
@@ -433,7 +703,14 @@ var ACTIONS = [
|
|
|
433
703
|
description: 'Outstanding receivables bucketed by age \u2014 answers "what is overdue and by how long".',
|
|
434
704
|
kind: "read",
|
|
435
705
|
scopes: ["invoices:read"],
|
|
436
|
-
input: [
|
|
706
|
+
input: [
|
|
707
|
+
{
|
|
708
|
+
name: "applicationId",
|
|
709
|
+
type: "string",
|
|
710
|
+
required: false,
|
|
711
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
712
|
+
}
|
|
713
|
+
],
|
|
437
714
|
requests: [{ method: "GET", path: "/api/v1/invoices/aging" }],
|
|
438
715
|
channels: ["mcp", "zapier", "make", "n8n"]
|
|
439
716
|
},
|
|
@@ -458,7 +735,13 @@ var ACTIONS = [
|
|
|
458
735
|
{ name: "createdAfter", type: "string", required: false, description: "ISO 8601 date lower bound." },
|
|
459
736
|
{ name: "createdBefore", type: "string", required: false, description: "ISO 8601 date upper bound." },
|
|
460
737
|
{ name: "limit", type: "number", required: false, description: "Page size." },
|
|
461
|
-
{ name: "cursor", type: "string", required: false, description: "Pagination cursor." }
|
|
738
|
+
{ name: "cursor", type: "string", required: false, description: "Pagination cursor." },
|
|
739
|
+
{
|
|
740
|
+
name: "applicationId",
|
|
741
|
+
type: "string",
|
|
742
|
+
required: false,
|
|
743
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
744
|
+
}
|
|
462
745
|
],
|
|
463
746
|
requests: [{ method: "GET", path: "/api/v1/discounts" }],
|
|
464
747
|
channels: ["mcp", "zapier", "make", "n8n"]
|
|
@@ -469,7 +752,15 @@ var ACTIONS = [
|
|
|
469
752
|
description: "Read a single discount code: value, conditions, usage limits and redemption count.",
|
|
470
753
|
kind: "read",
|
|
471
754
|
scopes: ["discounts:read"],
|
|
472
|
-
input: [
|
|
755
|
+
input: [
|
|
756
|
+
{ name: "id", type: "string", required: true, description: "Discount id." },
|
|
757
|
+
{
|
|
758
|
+
name: "applicationId",
|
|
759
|
+
type: "string",
|
|
760
|
+
required: false,
|
|
761
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
762
|
+
}
|
|
763
|
+
],
|
|
473
764
|
requests: [{ method: "GET", path: "/api/v1/discounts/:id" }],
|
|
474
765
|
channels: ["mcp", "zapier", "make", "n8n"]
|
|
475
766
|
},
|
|
@@ -496,7 +787,13 @@ var ACTIONS = [
|
|
|
496
787
|
{ name: "amountMin", type: "number", required: false, description: "Minimum amount, minor units." },
|
|
497
788
|
{ name: "amountMax", type: "number", required: false, description: "Maximum amount, minor units." },
|
|
498
789
|
{ name: "limit", type: "number", required: false, description: "Page size." },
|
|
499
|
-
{ name: "cursor", type: "string", required: false, description: "Pagination cursor." }
|
|
790
|
+
{ name: "cursor", type: "string", required: false, description: "Pagination cursor." },
|
|
791
|
+
{
|
|
792
|
+
name: "applicationId",
|
|
793
|
+
type: "string",
|
|
794
|
+
required: false,
|
|
795
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
796
|
+
}
|
|
500
797
|
],
|
|
501
798
|
requests: [{ method: "GET", path: "/api/v1/payments" }],
|
|
502
799
|
channels: ["mcp", "zapier", "make", "n8n"]
|
|
@@ -507,7 +804,15 @@ var ACTIONS = [
|
|
|
507
804
|
description: "Read a single payment plus its full transaction history (authorize/capture/refund/void attempts).",
|
|
508
805
|
kind: "read",
|
|
509
806
|
scopes: ["payments:read"],
|
|
510
|
-
input: [
|
|
807
|
+
input: [
|
|
808
|
+
{ name: "id", type: "string", required: true, description: "Payment id." },
|
|
809
|
+
{
|
|
810
|
+
name: "applicationId",
|
|
811
|
+
type: "string",
|
|
812
|
+
required: false,
|
|
813
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
814
|
+
}
|
|
815
|
+
],
|
|
511
816
|
requests: [
|
|
512
817
|
{ method: "GET", path: "/api/v1/payments/:id" },
|
|
513
818
|
{ method: "GET", path: "/api/v1/payments/:id/transactions" }
|
|
@@ -524,9 +829,18 @@ var ACTIONS = [
|
|
|
524
829
|
description: "Read a single issued invoice: line items, totals, status and the billing snapshot at issue time.",
|
|
525
830
|
kind: "read",
|
|
526
831
|
scopes: ["invoices:read"],
|
|
527
|
-
input: [
|
|
832
|
+
input: [
|
|
833
|
+
{ name: "id", type: "string", required: true, description: "Invoice id." },
|
|
834
|
+
{
|
|
835
|
+
name: "applicationId",
|
|
836
|
+
type: "string",
|
|
837
|
+
required: false,
|
|
838
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
839
|
+
}
|
|
840
|
+
],
|
|
528
841
|
requests: [{ method: "GET", path: "/api/v1/invoices/:id" }],
|
|
529
|
-
channels: ["mcp", "zapier", "make", "n8n"]
|
|
842
|
+
channels: ["mcp", "zapier", "make", "n8n"],
|
|
843
|
+
guardrails: [OAUTH_WITHHELD]
|
|
530
844
|
},
|
|
531
845
|
// ------------------------------------------------------------------ carts
|
|
532
846
|
{
|
|
@@ -538,7 +852,13 @@ var ACTIONS = [
|
|
|
538
852
|
input: [
|
|
539
853
|
{ name: "sinceDays", type: "number", required: false, description: "How many days back to look." },
|
|
540
854
|
{ name: "limit", type: "number", required: false, description: "Page size." },
|
|
541
|
-
{ name: "cursor", type: "string", required: false, description: "Pagination cursor." }
|
|
855
|
+
{ name: "cursor", type: "string", required: false, description: "Pagination cursor." },
|
|
856
|
+
{
|
|
857
|
+
name: "applicationId",
|
|
858
|
+
type: "string",
|
|
859
|
+
required: false,
|
|
860
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
861
|
+
}
|
|
542
862
|
],
|
|
543
863
|
requests: [
|
|
544
864
|
{ method: "GET", path: "/api/v1/abandoned-carts" },
|
|
@@ -559,7 +879,13 @@ var ACTIONS = [
|
|
|
559
879
|
{ name: "entityType", type: "string", required: false, description: "Entity type filter." },
|
|
560
880
|
{ name: "entityId", type: "string", required: false, description: "Entity id filter." },
|
|
561
881
|
{ name: "limit", type: "number", required: false, description: "Page size." },
|
|
562
|
-
{ name: "cursor", type: "string", required: false, description: "Pagination cursor." }
|
|
882
|
+
{ name: "cursor", type: "string", required: false, description: "Pagination cursor." },
|
|
883
|
+
{
|
|
884
|
+
name: "applicationId",
|
|
885
|
+
type: "string",
|
|
886
|
+
required: false,
|
|
887
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
888
|
+
}
|
|
563
889
|
],
|
|
564
890
|
requests: [{ method: "GET", path: "/api/v1/events" }],
|
|
565
891
|
channels: ["mcp", "n8n"]
|
|
@@ -567,13 +893,30 @@ var ACTIONS = [
|
|
|
567
893
|
{
|
|
568
894
|
id: "list_webhook_deliveries",
|
|
569
895
|
title: "List webhook deliveries",
|
|
570
|
-
description: "Recent outbound webhook deliveries, filterable by endpoint and
|
|
896
|
+
description: "Recent outbound webhook deliveries, filterable by endpoint, status and event type.",
|
|
571
897
|
kind: "read",
|
|
572
898
|
scopes: ["webhook_deliveries:read"],
|
|
573
899
|
input: [
|
|
574
900
|
{ name: "endpointId", type: "string", required: false, description: "Filter by endpoint." },
|
|
575
|
-
{
|
|
576
|
-
|
|
901
|
+
{
|
|
902
|
+
name: "status",
|
|
903
|
+
type: "string",
|
|
904
|
+
required: false,
|
|
905
|
+
description: "Filter by delivery status: pending, delivered, failed or dead_letter."
|
|
906
|
+
},
|
|
907
|
+
{
|
|
908
|
+
name: "eventType",
|
|
909
|
+
type: "string",
|
|
910
|
+
required: false,
|
|
911
|
+
description: "Filter by exact event type, e.g. payment.failed."
|
|
912
|
+
},
|
|
913
|
+
{ name: "limit", type: "number", required: false, description: "Page size." },
|
|
914
|
+
{
|
|
915
|
+
name: "applicationId",
|
|
916
|
+
type: "string",
|
|
917
|
+
required: false,
|
|
918
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
919
|
+
}
|
|
577
920
|
],
|
|
578
921
|
requests: [{ method: "GET", path: "/api/v1/webhook-deliveries" }],
|
|
579
922
|
channels: ["mcp", "n8n"]
|
|
@@ -584,7 +927,15 @@ var ACTIONS = [
|
|
|
584
927
|
description: "Full delivery record: the signed payload we sent, the response we got back, and every attempt.",
|
|
585
928
|
kind: "read",
|
|
586
929
|
scopes: ["webhook_deliveries:read"],
|
|
587
|
-
input: [
|
|
930
|
+
input: [
|
|
931
|
+
{ name: "id", type: "string", required: true, description: "Delivery id." },
|
|
932
|
+
{
|
|
933
|
+
name: "applicationId",
|
|
934
|
+
type: "string",
|
|
935
|
+
required: false,
|
|
936
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
937
|
+
}
|
|
938
|
+
],
|
|
588
939
|
requests: [{ method: "GET", path: "/api/v1/webhook-deliveries/:id" }],
|
|
589
940
|
channels: ["mcp", "n8n"]
|
|
590
941
|
},
|
|
@@ -594,12 +945,19 @@ var ACTIONS = [
|
|
|
594
945
|
description: "Diagnose a failing checkout in one call: session state, its cart, the events it emitted, and any webhook deliveries that followed. Use this before asking the user to reproduce anything.",
|
|
595
946
|
kind: "read",
|
|
596
947
|
scopes: ["checkout_sessions:read"],
|
|
948
|
+
sectionScopes: ["events:read", "webhook_deliveries:read"],
|
|
597
949
|
input: [
|
|
598
950
|
{
|
|
599
951
|
name: "id",
|
|
600
952
|
type: "string",
|
|
601
953
|
required: true,
|
|
602
954
|
description: "Checkout session id (or cart id \u2014 the tool resolves the session)."
|
|
955
|
+
},
|
|
956
|
+
{
|
|
957
|
+
name: "applicationId",
|
|
958
|
+
type: "string",
|
|
959
|
+
required: false,
|
|
960
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
603
961
|
}
|
|
604
962
|
],
|
|
605
963
|
requests: [
|
|
@@ -610,7 +968,8 @@ var ACTIONS = [
|
|
|
610
968
|
],
|
|
611
969
|
channels: ["mcp"],
|
|
612
970
|
guardrails: [
|
|
613
|
-
"Report which sections were unavailable rather than failing the call when a sub-read is unscoped."
|
|
971
|
+
"Report which sections were unavailable rather than failing the call when a sub-read is unscoped.",
|
|
972
|
+
OAUTH_WITHHELD
|
|
614
973
|
]
|
|
615
974
|
},
|
|
616
975
|
// ------------------------------------------------------------- extensions
|
|
@@ -629,7 +988,13 @@ var ACTIONS = [
|
|
|
629
988
|
enum: ["active", "suspended", "uninstalled"]
|
|
630
989
|
},
|
|
631
990
|
{ name: "limit", type: "number", required: false, description: "Page size." },
|
|
632
|
-
{ name: "cursor", type: "string", required: false, description: "Pagination cursor." }
|
|
991
|
+
{ name: "cursor", type: "string", required: false, description: "Pagination cursor." },
|
|
992
|
+
{
|
|
993
|
+
name: "applicationId",
|
|
994
|
+
type: "string",
|
|
995
|
+
required: false,
|
|
996
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
997
|
+
}
|
|
633
998
|
],
|
|
634
999
|
requests: [{ method: "GET", path: "/api/v1/installations" }],
|
|
635
1000
|
channels: ["mcp", "zapier", "make", "n8n"]
|
|
@@ -641,7 +1006,14 @@ var ACTIONS = [
|
|
|
641
1006
|
description: "Read the payment-routing cascade, active routing connectors, and card/other decline rules \u2014 what determines which processor handles a payment and what happens on a decline.",
|
|
642
1007
|
kind: "read",
|
|
643
1008
|
scopes: ["connectors:read"],
|
|
644
|
-
input: [
|
|
1009
|
+
input: [
|
|
1010
|
+
{
|
|
1011
|
+
name: "applicationId",
|
|
1012
|
+
type: "string",
|
|
1013
|
+
required: false,
|
|
1014
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
1015
|
+
}
|
|
1016
|
+
],
|
|
645
1017
|
requests: [
|
|
646
1018
|
{ method: "GET", path: "/api/v1/payment-routing" },
|
|
647
1019
|
{ method: "GET", path: "/api/v1/routing-connectors" },
|
|
@@ -659,7 +1031,15 @@ var ACTIONS = [
|
|
|
659
1031
|
description: 'Answers "am I ready to go live?" \u2014 active connectors, webhook endpoints and their recent delivery health, and the environment this credential is pinned to.',
|
|
660
1032
|
kind: "read",
|
|
661
1033
|
scopes: [],
|
|
662
|
-
|
|
1034
|
+
sectionScopes: ["connectors:read", "webhooks:read"],
|
|
1035
|
+
input: [
|
|
1036
|
+
{
|
|
1037
|
+
name: "applicationId",
|
|
1038
|
+
type: "string",
|
|
1039
|
+
required: false,
|
|
1040
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
1041
|
+
}
|
|
1042
|
+
],
|
|
663
1043
|
requests: [
|
|
664
1044
|
{ method: "GET", path: "/api/v1/whoami" },
|
|
665
1045
|
{ method: "GET", path: "/api/v1/connectors" },
|
|
@@ -675,7 +1055,7 @@ var ACTIONS = [
|
|
|
675
1055
|
{
|
|
676
1056
|
id: "get_workspace_settings",
|
|
677
1057
|
title: "Get workspace settings",
|
|
678
|
-
description: "Read this workspace's identity
|
|
1058
|
+
description: "Read this workspace's identity: name, slug, branding and onboarding state. Payment terms are set per application and environment, not here.",
|
|
679
1059
|
kind: "read",
|
|
680
1060
|
scopes: ["merchant:read"],
|
|
681
1061
|
input: [],
|
|
@@ -694,12 +1074,48 @@ var ACTIONS = [
|
|
|
694
1074
|
requests: [{ method: "GET", path: "/api/v1/audit-log" }],
|
|
695
1075
|
channels: ["mcp", "zapier", "make", "n8n"]
|
|
696
1076
|
},
|
|
1077
|
+
{
|
|
1078
|
+
id: "list_applications",
|
|
1079
|
+
title: "List applications in this workspace",
|
|
1080
|
+
description: 'List every application in this workspace you have access to, so you can pass one of their ids as applicationId on other read and analytics tools. Note: the underlying route is the legacy-named /v1/stores (kept for contract stability) \u2014 the resource it returns is applications, not a separate "stores" concept.',
|
|
1081
|
+
kind: "read",
|
|
1082
|
+
scopes: ["applications:read"],
|
|
1083
|
+
input: [],
|
|
1084
|
+
requests: [{ method: "GET", path: "/api/v1/stores" }],
|
|
1085
|
+
channels: ["mcp", "zapier", "make", "n8n"]
|
|
1086
|
+
},
|
|
1087
|
+
{
|
|
1088
|
+
id: "get_sales_timeseries",
|
|
1089
|
+
title: "Sales over time",
|
|
1090
|
+
description: `Order count and gross revenue bucketed by day, week or month over any date range, in the merchant's timezone, optionally compared to the previous period or the same range last year. Use this for "how much did we sell on/last/between\u2026" instead of paging search_orders and adding it up.`,
|
|
1091
|
+
kind: "read",
|
|
1092
|
+
scopes: ["orders:read"],
|
|
1093
|
+
input: [
|
|
1094
|
+
{ name: "from", type: "string", required: false, description: 'ISO 8601 start of range, exclusive. Defaults to 30 days before "to".' },
|
|
1095
|
+
{ name: "to", type: "string", required: false, description: "ISO 8601 end of range, exclusive. Defaults to now." },
|
|
1096
|
+
{ name: "groupBy", type: "string", required: false, description: "Bucket size.", enum: ["day", "week", "month"], default: "day" },
|
|
1097
|
+
{ name: "timezone", type: "string", required: false, description: "IANA timezone for bucket boundaries, e.g. America/New_York. Defaults to UTC \u2014 ask the user rather than assuming UTC when the answer depends on day boundaries." },
|
|
1098
|
+
{ name: "compareTo", type: "string", required: false, description: "Add a comparison total.", enum: ["previous_period", "previous_year", "none"], default: "none" },
|
|
1099
|
+
{
|
|
1100
|
+
name: "applicationId",
|
|
1101
|
+
type: "string",
|
|
1102
|
+
required: false,
|
|
1103
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
1104
|
+
}
|
|
1105
|
+
],
|
|
1106
|
+
requests: [{ method: "GET", path: "/api/v1/analytics/orders-timeseries" }],
|
|
1107
|
+
channels: ["mcp", "zapier", "make", "n8n"],
|
|
1108
|
+
guardrails: [
|
|
1109
|
+
"Revenue is GROSS. Refunds are not deducted, because that needs payment_refunds:read, which OAuth connections cannot hold. Never describe the figure as net."
|
|
1110
|
+
]
|
|
1111
|
+
},
|
|
697
1112
|
{
|
|
698
1113
|
id: "get_business_summary",
|
|
699
1114
|
title: "Business summary",
|
|
700
|
-
description: "How are we doing? Orders/revenue, subscription MRR, receivables and quotes pipeline
|
|
1115
|
+
description: "How are we doing? Orders/revenue, subscription MRR, receivables and quotes pipeline over a date range, compared to the immediately preceding window of equal length. Note: period=week|month|quarter means the TRAILING 7/30/90 days, not a calendar period \u2014 pass from/to for calendar months.",
|
|
701
1116
|
kind: "read",
|
|
702
1117
|
scopes: [],
|
|
1118
|
+
sectionScopes: ["orders:read", "subscriptions:read", "invoices:read", "quotes:read"],
|
|
703
1119
|
input: [
|
|
704
1120
|
{
|
|
705
1121
|
name: "period",
|
|
@@ -708,6 +1124,15 @@ var ACTIONS = [
|
|
|
708
1124
|
description: "Reporting window.",
|
|
709
1125
|
enum: ["week", "month", "quarter"],
|
|
710
1126
|
default: "month"
|
|
1127
|
+
},
|
|
1128
|
+
{ name: "from", type: "string", required: false, description: "ISO 8601 start of range. Overrides period." },
|
|
1129
|
+
{ name: "to", type: "string", required: false, description: "ISO 8601 end of range. Defaults to now." },
|
|
1130
|
+
{ name: "timezone", type: "string", required: false, description: "IANA timezone for period boundaries. Defaults to UTC." },
|
|
1131
|
+
{
|
|
1132
|
+
name: "applicationId",
|
|
1133
|
+
type: "string",
|
|
1134
|
+
required: false,
|
|
1135
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
711
1136
|
}
|
|
712
1137
|
],
|
|
713
1138
|
requests: [{ method: "GET", path: "/api/v1/analytics/business-summary" }],
|
|
@@ -716,19 +1141,189 @@ var ACTIONS = [
|
|
|
716
1141
|
"Each section needs its own scope (orders:read, subscriptions:read, invoices:read, quotes:read). A section without its scope is reported as omitted rather than failing the whole call."
|
|
717
1142
|
]
|
|
718
1143
|
},
|
|
1144
|
+
{
|
|
1145
|
+
id: "get_top_products",
|
|
1146
|
+
title: "Top products",
|
|
1147
|
+
description: "Best-selling products over a date range, by revenue or units sold. Line items come from the merchant's own cart payload, so a product is identified by its external reference id when present and by name otherwise. The response carries both the top-N rows (by the chosen sort) and range-wide totals across every matching product, so you can compute what share of total revenue/units the top N represent.",
|
|
1148
|
+
kind: "read",
|
|
1149
|
+
scopes: ["orders:read"],
|
|
1150
|
+
input: [
|
|
1151
|
+
{ name: "from", type: "string", required: false, description: 'ISO 8601 start of range, exclusive. Defaults to 30 days before "to".' },
|
|
1152
|
+
{ name: "to", type: "string", required: false, description: "ISO 8601 end of range, exclusive. Defaults to now." },
|
|
1153
|
+
{ name: "timezone", type: "string", required: false, description: "IANA timezone for range boundaries. Defaults to UTC." },
|
|
1154
|
+
{ name: "by", type: "string", required: false, description: "Sort key.", enum: ["revenue", "units"], default: "revenue" },
|
|
1155
|
+
{ name: "limit", type: "number", required: false, description: "Max rows to return (1-100). Defaults to 20." },
|
|
1156
|
+
{
|
|
1157
|
+
name: "applicationId",
|
|
1158
|
+
type: "string",
|
|
1159
|
+
required: false,
|
|
1160
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
1161
|
+
}
|
|
1162
|
+
],
|
|
1163
|
+
requests: [{ method: "GET", path: "/api/v1/analytics/products" }],
|
|
1164
|
+
channels: ["mcp", "zapier", "make", "n8n"],
|
|
1165
|
+
guardrails: [
|
|
1166
|
+
"Revenue is GROSS. Refunds are not deducted, because that needs payment_refunds:read, which OAuth connections cannot hold. Never describe the figure as net."
|
|
1167
|
+
]
|
|
1168
|
+
},
|
|
719
1169
|
{
|
|
720
1170
|
id: "get_attention_digest",
|
|
721
1171
|
title: "Attention digest",
|
|
722
1172
|
description: "What needs attention right now? Open disputes, subscriptions in dunning, overdue Net-N invoices, and webhooks with degraded delivery health \u2014 a point-in-time snapshot, not a time range.",
|
|
723
1173
|
kind: "read",
|
|
724
1174
|
scopes: [],
|
|
725
|
-
|
|
1175
|
+
sectionScopes: ["payment_disputes:read", "subscriptions:read", "invoices:read", "webhook_deliveries:read"],
|
|
1176
|
+
input: [
|
|
1177
|
+
{
|
|
1178
|
+
name: "applicationId",
|
|
1179
|
+
type: "string",
|
|
1180
|
+
required: false,
|
|
1181
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
1182
|
+
}
|
|
1183
|
+
],
|
|
726
1184
|
requests: [{ method: "GET", path: "/api/v1/analytics/attention-digest" }],
|
|
727
1185
|
channels: ["mcp", "zapier", "make", "n8n"],
|
|
728
1186
|
guardrails: [
|
|
729
1187
|
"Each section needs its own scope (payment_disputes:read, subscriptions:read, invoices:read, webhook_deliveries:read). A section without its scope is reported as omitted rather than failing the whole call."
|
|
730
1188
|
]
|
|
731
1189
|
},
|
|
1190
|
+
{
|
|
1191
|
+
id: "get_customer_growth",
|
|
1192
|
+
title: "Customer growth",
|
|
1193
|
+
description: "Customer growth over a date range: signups per day/week/month, how many orders came from first-time versus returning customers, and the highest-value customers in the window.",
|
|
1194
|
+
kind: "read",
|
|
1195
|
+
scopes: ["customers:read"],
|
|
1196
|
+
input: [
|
|
1197
|
+
{ name: "from", type: "string", required: false, description: 'ISO 8601 start of range, exclusive. Defaults to 30 days before "to".' },
|
|
1198
|
+
{ name: "to", type: "string", required: false, description: "ISO 8601 end of range, exclusive. Defaults to now." },
|
|
1199
|
+
{ name: "groupBy", type: "string", required: false, description: "Signup series bucket size.", enum: ["day", "week", "month"], default: "day" },
|
|
1200
|
+
{ name: "timezone", type: "string", required: false, description: "IANA timezone for bucket boundaries. Defaults to UTC." },
|
|
1201
|
+
{ name: "topBy", type: "string", required: false, description: "Sort key for top customers.", enum: ["revenue", "orders"], default: "revenue" },
|
|
1202
|
+
{ name: "topLimit", type: "number", required: false, description: "Max top-customers rows to return (1-50). Defaults to 10." },
|
|
1203
|
+
{
|
|
1204
|
+
name: "applicationId",
|
|
1205
|
+
type: "string",
|
|
1206
|
+
required: false,
|
|
1207
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
1208
|
+
}
|
|
1209
|
+
],
|
|
1210
|
+
requests: [{ method: "GET", path: "/api/v1/analytics/customers" }],
|
|
1211
|
+
channels: ["mcp", "zapier", "make", "n8n"],
|
|
1212
|
+
guardrails: [
|
|
1213
|
+
"Revenue is GROSS. Refunds are not deducted, because that needs payment_refunds:read, which OAuth connections cannot hold. Never describe the figure as net.",
|
|
1214
|
+
"topCustomers is a top-N list; use the accompanying totals for range-wide customer/order/revenue counts instead of summing the returned rows.",
|
|
1215
|
+
"topCustomers[].email and .name come back null unless the credential holds customer_pii:read, which OAuth connections cannot hold. Identify a top customer by id, not by a null name."
|
|
1216
|
+
]
|
|
1217
|
+
},
|
|
1218
|
+
{
|
|
1219
|
+
id: "get_revenue_breakdown",
|
|
1220
|
+
title: "Revenue breakdown",
|
|
1221
|
+
description: "Revenue split by one dimension over a date range \u2014 payment method, order source, or currency. Amounts are gross; refunds are not deducted.",
|
|
1222
|
+
kind: "read",
|
|
1223
|
+
scopes: ["orders:read"],
|
|
1224
|
+
input: [
|
|
1225
|
+
{
|
|
1226
|
+
name: "dimension",
|
|
1227
|
+
type: "string",
|
|
1228
|
+
required: true,
|
|
1229
|
+
description: "Breakdown dimension.",
|
|
1230
|
+
enum: ["paymentMethod", "source", "currency"]
|
|
1231
|
+
},
|
|
1232
|
+
{ name: "from", type: "string", required: false, description: 'ISO 8601 start of range, exclusive. Defaults to 30 days before "to".' },
|
|
1233
|
+
{ name: "to", type: "string", required: false, description: "ISO 8601 end of range, exclusive. Defaults to now." },
|
|
1234
|
+
{ name: "timezone", type: "string", required: false, description: "IANA timezone for range boundaries. Defaults to UTC." },
|
|
1235
|
+
{
|
|
1236
|
+
name: "applicationId",
|
|
1237
|
+
type: "string",
|
|
1238
|
+
required: false,
|
|
1239
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
1240
|
+
}
|
|
1241
|
+
],
|
|
1242
|
+
requests: [{ method: "GET", path: "/api/v1/analytics/revenue-breakdown" }],
|
|
1243
|
+
channels: ["mcp", "zapier", "make", "n8n"],
|
|
1244
|
+
guardrails: [
|
|
1245
|
+
"Revenue is GROSS. Refunds are not deducted, because that needs payment_refunds:read, which OAuth connections cannot hold. Never describe the figure as net.",
|
|
1246
|
+
"dimension is required; it is never defaulted, so omitting it fails the call instead of silently picking a dimension.",
|
|
1247
|
+
"For dimension=paymentMethod, an order paid via more than one method can appear in more than one row, so rows can sum to more than totals (a separate whole-range aggregate over orders alone)."
|
|
1248
|
+
]
|
|
1249
|
+
},
|
|
1250
|
+
{
|
|
1251
|
+
id: "get_funnel_conversion",
|
|
1252
|
+
title: "Checkout funnel",
|
|
1253
|
+
description: "Checkout funnel over a date range: carts created, checkout sessions started, orders completed, with the drop-off between each stage.",
|
|
1254
|
+
kind: "read",
|
|
1255
|
+
scopes: ["carts:read"],
|
|
1256
|
+
input: [
|
|
1257
|
+
{ name: "from", type: "string", required: false, description: 'ISO 8601 start of range, exclusive. Defaults to 30 days before "to".' },
|
|
1258
|
+
{ name: "to", type: "string", required: false, description: "ISO 8601 end of range, exclusive. Defaults to now." },
|
|
1259
|
+
{ name: "timezone", type: "string", required: false, description: "IANA timezone for range boundaries. Defaults to UTC." },
|
|
1260
|
+
{
|
|
1261
|
+
name: "applicationId",
|
|
1262
|
+
type: "string",
|
|
1263
|
+
required: false,
|
|
1264
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
1265
|
+
}
|
|
1266
|
+
],
|
|
1267
|
+
requests: [{ method: "GET", path: "/api/v1/analytics/funnel" }],
|
|
1268
|
+
channels: ["mcp", "zapier", "make", "n8n"],
|
|
1269
|
+
guardrails: [
|
|
1270
|
+
"Each stage needs its own scope (carts:read, checkout_sessions:read, orders:read), checked independently \u2014 an OAuth grant carries literal scopes with no legacy expansion, so a credential can hold carts:read without the other two. An omitted stage is reported as such rather than failing the whole call.",
|
|
1271
|
+
"conversionRate and dropOff[].ratePct are null (unknown) when a stage they depend on is omitted, and 0 only when the stage was readable with a true zero count. Never treat a null here as 0.",
|
|
1272
|
+
'dropOff[].ratePct is the percentage of "from" that did NOT reach "to" (loss), not a conversion rate. The step-conversion rate is 100 - ratePct.'
|
|
1273
|
+
]
|
|
1274
|
+
},
|
|
1275
|
+
{
|
|
1276
|
+
id: "get_subscription_movement",
|
|
1277
|
+
title: "Subscription movement",
|
|
1278
|
+
description: "Subscription movement over a date range: new subscriptions, cancellations and net MRR change per day/week/month. Figures are reconstructed from subscription timestamps, not a stored historical snapshot.",
|
|
1279
|
+
kind: "read",
|
|
1280
|
+
scopes: ["subscriptions:read"],
|
|
1281
|
+
input: [
|
|
1282
|
+
{ name: "from", type: "string", required: false, description: 'ISO 8601 start of range, exclusive. Defaults to 30 days before "to".' },
|
|
1283
|
+
{ name: "to", type: "string", required: false, description: "ISO 8601 end of range, exclusive. Defaults to now." },
|
|
1284
|
+
{ name: "timezone", type: "string", required: false, description: "IANA timezone for bucket boundaries. Defaults to UTC." },
|
|
1285
|
+
{ name: "groupBy", type: "string", required: false, description: "Bucket size.", enum: ["day", "week", "month"], default: "day" },
|
|
1286
|
+
{
|
|
1287
|
+
name: "applicationId",
|
|
1288
|
+
type: "string",
|
|
1289
|
+
required: false,
|
|
1290
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
1291
|
+
}
|
|
1292
|
+
],
|
|
1293
|
+
requests: [{ method: "GET", path: "/api/v1/analytics/subscriptions" }],
|
|
1294
|
+
channels: ["mcp", "zapier", "make", "n8n"],
|
|
1295
|
+
guardrails: [
|
|
1296
|
+
"newSubscriptions, churnedSubscriptions and mrrDeltaCents are reconstructed per bucket from subscription created_at/cancelled_at timestamps, not a stored historical MRR snapshot \u2014 never present this series as an audited historical MRR ledger.",
|
|
1297
|
+
"mrrDeltaCents for a bucket only credits subscriptions created in that bucket that are still active right now, and only debits subscriptions cancelled in that bucket that already existed when it started. A subscription created AND cancelled inside the same bucket still counts toward both newSubscriptions and churnedSubscriptions but contributes 0 to mrrDeltaCents.",
|
|
1298
|
+
"totals is the sum over the full series (the series is never truncated), so totals.newSubscriptions/.churnedSubscriptions/.netMrrDeltaCents always equal the corresponding sums of series[]."
|
|
1299
|
+
]
|
|
1300
|
+
},
|
|
1301
|
+
{
|
|
1302
|
+
id: "get_discount_performance",
|
|
1303
|
+
title: "Discount performance",
|
|
1304
|
+
description: "Discount code performance over a date range: redemption count, revenue on discounted orders, and total discount given per code.",
|
|
1305
|
+
kind: "read",
|
|
1306
|
+
scopes: ["discounts:read"],
|
|
1307
|
+
input: [
|
|
1308
|
+
{ name: "from", type: "string", required: false, description: 'ISO 8601 start of range, exclusive. Defaults to 30 days before "to".' },
|
|
1309
|
+
{ name: "to", type: "string", required: false, description: "ISO 8601 end of range, exclusive. Defaults to now." },
|
|
1310
|
+
{ name: "timezone", type: "string", required: false, description: "IANA timezone for range boundaries. Defaults to UTC." },
|
|
1311
|
+
{ name: "limit", type: "number", required: false, description: "Max rows to return. Defaults to 20." },
|
|
1312
|
+
{
|
|
1313
|
+
name: "applicationId",
|
|
1314
|
+
type: "string",
|
|
1315
|
+
required: false,
|
|
1316
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
1317
|
+
}
|
|
1318
|
+
],
|
|
1319
|
+
requests: [{ method: "GET", path: "/api/v1/analytics/discounts" }],
|
|
1320
|
+
channels: ["mcp", "zapier", "make", "n8n"],
|
|
1321
|
+
guardrails: [
|
|
1322
|
+
'discountedRevenueCents and discountGivenCents are DIFFERENT measures: discountedRevenueCents is the gross order total on orders that redeemed the code; discountGivenCents is the discount amount taken off those same orders. Never describe discountedRevenueCents as "given away" or discountGivenCents as revenue.',
|
|
1323
|
+
"Only orders with status <> draft and a gross-eligible payment_status are counted; refunds are not deducted from discountedRevenueCents, because that needs payment_refunds:read.",
|
|
1324
|
+
"rows are the top N codes by discount given; totals is a SEPARATE whole-range aggregate (not a reduction over rows), so totals.redemptions / totals.discountGivenCents stay valid even when rows is truncated by limit."
|
|
1325
|
+
]
|
|
1326
|
+
},
|
|
732
1327
|
// ------------------------------------------------------------ sandbox write
|
|
733
1328
|
{
|
|
734
1329
|
id: "seed_test_data",
|
|
@@ -754,6 +1349,151 @@ var ACTIONS = [
|
|
|
754
1349
|
"Seeds a finalized DRAFT order \u2014 it does not capture a payment, which needs a browser-side tokenised payment method."
|
|
755
1350
|
]
|
|
756
1351
|
},
|
|
1352
|
+
// ---------------------------------------------------------- inbound writes
|
|
1353
|
+
// The no-code channels are mostly used in the "something happened elsewhere,
|
|
1354
|
+
// put it into Throttle" direction — a form submission, a new CRM contact, a
|
|
1355
|
+
// spreadsheet row. These three are that direction.
|
|
1356
|
+
{
|
|
1357
|
+
id: "create_customer",
|
|
1358
|
+
title: "Create a customer",
|
|
1359
|
+
description: "Create a customer record. Use search_customers first if the buyer may already exist \u2014 this does not de-duplicate by email.",
|
|
1360
|
+
kind: "write",
|
|
1361
|
+
scopes: ["customers:write"],
|
|
1362
|
+
input: [
|
|
1363
|
+
{ name: "email", type: "string", required: true, description: "The buyer's email address." },
|
|
1364
|
+
{ name: "firstName", type: "string", required: false, description: "Given name." },
|
|
1365
|
+
{ name: "lastName", type: "string", required: false, description: "Family name." },
|
|
1366
|
+
{ name: "phone", type: "string", required: false, description: "Contact phone number." },
|
|
1367
|
+
{ name: "company", type: "string", required: false, description: "The buyer's company." },
|
|
1368
|
+
{
|
|
1369
|
+
name: "externalId",
|
|
1370
|
+
type: "string",
|
|
1371
|
+
required: false,
|
|
1372
|
+
description: "This customer's id in your own system. Resolvable later via get_customer."
|
|
1373
|
+
},
|
|
1374
|
+
{
|
|
1375
|
+
name: "netN",
|
|
1376
|
+
type: "number",
|
|
1377
|
+
required: false,
|
|
1378
|
+
description: "Invoice terms in days for this customer specifically, 0-365. Omit to use the workspace default; 0 means due on issue."
|
|
1379
|
+
},
|
|
1380
|
+
{
|
|
1381
|
+
name: "acceptsMarketing",
|
|
1382
|
+
type: "boolean",
|
|
1383
|
+
required: false,
|
|
1384
|
+
description: "Whether the buyer opted into marketing email.",
|
|
1385
|
+
default: false
|
|
1386
|
+
}
|
|
1387
|
+
],
|
|
1388
|
+
requests: [{ method: "POST", path: "/api/v1/customers" }],
|
|
1389
|
+
channels: ["mcp", "zapier", "make", "n8n"],
|
|
1390
|
+
idempotent: true,
|
|
1391
|
+
guardrails: [
|
|
1392
|
+
"The created record is echoed back with email, phone, firstName, lastName and name nulled unless the credential holds customer_pii:read, which OAuth connections cannot hold \u2014 the customer is still created with the values sent, only the response is redacted."
|
|
1393
|
+
]
|
|
1394
|
+
},
|
|
1395
|
+
{
|
|
1396
|
+
id: "update_customer",
|
|
1397
|
+
title: "Update a customer",
|
|
1398
|
+
description: "Change fields on an existing customer. Only the fields supplied are touched; everything else is left alone.",
|
|
1399
|
+
kind: "write",
|
|
1400
|
+
scopes: ["customers:write"],
|
|
1401
|
+
input: [
|
|
1402
|
+
{ name: "id", type: "string", required: true, description: "Customer id." },
|
|
1403
|
+
{ name: "email", type: "string", required: false, description: "The buyer's email address." },
|
|
1404
|
+
{ name: "firstName", type: "string", required: false, description: "Given name." },
|
|
1405
|
+
{ name: "lastName", type: "string", required: false, description: "Family name." },
|
|
1406
|
+
{ name: "phone", type: "string", required: false, description: "Contact phone number." },
|
|
1407
|
+
{ name: "company", type: "string", required: false, description: "The buyer's company." },
|
|
1408
|
+
{
|
|
1409
|
+
name: "externalId",
|
|
1410
|
+
type: "string",
|
|
1411
|
+
required: false,
|
|
1412
|
+
description: "This customer's id in your own system. Pass null to clear it."
|
|
1413
|
+
},
|
|
1414
|
+
{
|
|
1415
|
+
name: "netN",
|
|
1416
|
+
type: "number",
|
|
1417
|
+
required: false,
|
|
1418
|
+
description: "Invoice terms in days for this customer specifically, 0-365."
|
|
1419
|
+
},
|
|
1420
|
+
{
|
|
1421
|
+
name: "acceptsMarketing",
|
|
1422
|
+
type: "boolean",
|
|
1423
|
+
required: false,
|
|
1424
|
+
description: "Whether the buyer opted into marketing email."
|
|
1425
|
+
}
|
|
1426
|
+
],
|
|
1427
|
+
requests: [{ method: "PATCH", path: "/api/v1/customers/:id" }],
|
|
1428
|
+
channels: ["mcp", "zapier", "make", "n8n"],
|
|
1429
|
+
idempotent: true,
|
|
1430
|
+
guardrails: [
|
|
1431
|
+
"The updated record is echoed back with email, phone, firstName, lastName and name nulled unless the credential holds customer_pii:read, which OAuth connections cannot hold \u2014 the update still applied, only the response is redacted."
|
|
1432
|
+
]
|
|
1433
|
+
},
|
|
1434
|
+
{
|
|
1435
|
+
id: "create_checkout_link",
|
|
1436
|
+
title: "Create a checkout link",
|
|
1437
|
+
description: "Build a cart from line items and return a hosted checkout URL the buyer can pay at. Prices are in minor units (2500 = $25.00).",
|
|
1438
|
+
kind: "write",
|
|
1439
|
+
scopes: ["carts:write", "cart_items:write", "checkout_sessions:write"],
|
|
1440
|
+
input: [
|
|
1441
|
+
{
|
|
1442
|
+
name: "lineItems",
|
|
1443
|
+
type: "array",
|
|
1444
|
+
required: true,
|
|
1445
|
+
description: "What the buyer is paying for: { name, unitPrice, quantity }. unitPrice is an integer in minor units."
|
|
1446
|
+
},
|
|
1447
|
+
{
|
|
1448
|
+
name: "returnUrl",
|
|
1449
|
+
type: "string",
|
|
1450
|
+
required: true,
|
|
1451
|
+
description: "Absolute URL the buyer lands on after paying."
|
|
1452
|
+
},
|
|
1453
|
+
{
|
|
1454
|
+
name: "cancelUrl",
|
|
1455
|
+
type: "string",
|
|
1456
|
+
required: true,
|
|
1457
|
+
description: "Absolute URL the buyer lands on if they abandon checkout."
|
|
1458
|
+
},
|
|
1459
|
+
{
|
|
1460
|
+
name: "customerId",
|
|
1461
|
+
type: "string",
|
|
1462
|
+
required: false,
|
|
1463
|
+
description: "Attach the cart to an existing customer."
|
|
1464
|
+
},
|
|
1465
|
+
{
|
|
1466
|
+
name: "customerEmail",
|
|
1467
|
+
type: "string",
|
|
1468
|
+
required: false,
|
|
1469
|
+
description: "Pre-fill the buyer email without needing a customer record."
|
|
1470
|
+
},
|
|
1471
|
+
{
|
|
1472
|
+
name: "discountCode",
|
|
1473
|
+
type: "string",
|
|
1474
|
+
required: false,
|
|
1475
|
+
description: "Discount code to apply to the session."
|
|
1476
|
+
},
|
|
1477
|
+
{
|
|
1478
|
+
name: "netN",
|
|
1479
|
+
type: "number",
|
|
1480
|
+
required: false,
|
|
1481
|
+
description: "Invoice terms in days for this cart, 0-365. Omit to pay in full at checkout."
|
|
1482
|
+
}
|
|
1483
|
+
],
|
|
1484
|
+
// Three calls: the API has no single "checkout link" endpoint, and a
|
|
1485
|
+
// checkout session requires a cart that already holds its line items.
|
|
1486
|
+
requests: [
|
|
1487
|
+
{ method: "POST", path: "/api/v1/carts" },
|
|
1488
|
+
{ method: "POST", path: "/api/v1/carts/:id/items" },
|
|
1489
|
+
{ method: "POST", path: "/api/v1/checkout/sessions" }
|
|
1490
|
+
],
|
|
1491
|
+
channels: ["mcp", "zapier", "make", "n8n"],
|
|
1492
|
+
idempotent: true,
|
|
1493
|
+
guardrails: [
|
|
1494
|
+
"The returned URL is buyer-facing and takes real payment in a production environment."
|
|
1495
|
+
]
|
|
1496
|
+
},
|
|
757
1497
|
// -------------------------------------------------------------- money/mail
|
|
758
1498
|
{
|
|
759
1499
|
id: "create_quote",
|
|
@@ -782,7 +1522,8 @@ var ACTIONS = [
|
|
|
782
1522
|
requests: [{ method: "POST", path: "/api/v1/quotes/:id/issue" }],
|
|
783
1523
|
channels: ["mcp", "zapier", "make", "n8n"],
|
|
784
1524
|
idempotent: true,
|
|
785
|
-
guardrails: ['Sends mail to a real buyer. Never call it speculatively or to "check" a quote.']
|
|
1525
|
+
guardrails: ['Sends mail to a real buyer. Never call it speculatively or to "check" a quote.'],
|
|
1526
|
+
destructive: true
|
|
786
1527
|
},
|
|
787
1528
|
{
|
|
788
1529
|
id: "refund_payment",
|
|
@@ -801,12 +1542,20 @@ var ACTIONS = [
|
|
|
801
1542
|
{ name: "reason", type: "string", required: false, description: "Reason recorded on the refund." }
|
|
802
1543
|
],
|
|
803
1544
|
requests: [{ method: "POST", path: "/api/v1/payments/:id/refund" }],
|
|
804
|
-
|
|
1545
|
+
// NOT zapier/make. `payment_refunds:write` is `sensitive` in the scope
|
|
1546
|
+
// registry, and `validateScopesForOAuth` rejects every sensitive scope — so
|
|
1547
|
+
// an OAuth grant, which is the only credential a public Zapier or Make app
|
|
1548
|
+
// can hold, can never carry it. Listing the action on those channels would
|
|
1549
|
+
// ship a connector action that 403s on every call. MCP and n8n keep it:
|
|
1550
|
+
// both authenticate with an `sk_` key, which CAN hold the scope.
|
|
1551
|
+
// `oauth-grantable-scopes` in the catalog tests enforces this.
|
|
1552
|
+
channels: ["mcp", "n8n"],
|
|
805
1553
|
idempotent: true,
|
|
806
1554
|
guardrails: [
|
|
807
1555
|
"Moves money and is irreversible. Confirm with the user first.",
|
|
808
1556
|
"The API treats an omitted amount as a FULL refund, so this action requires it explicitly."
|
|
809
|
-
]
|
|
1557
|
+
],
|
|
1558
|
+
destructive: true
|
|
810
1559
|
},
|
|
811
1560
|
{
|
|
812
1561
|
id: "manage_subscription",
|
|
@@ -849,7 +1598,8 @@ var ACTIONS = [
|
|
|
849
1598
|
requests: [{ method: "POST", path: "/api/v1/subscriptions/:id/cancel" }],
|
|
850
1599
|
channels: ["mcp", "zapier", "make", "n8n"],
|
|
851
1600
|
idempotent: true,
|
|
852
|
-
guardrails: ["Ends billing for a paying customer. Confirm with the user first."]
|
|
1601
|
+
guardrails: ["Ends billing for a paying customer. Confirm with the user first."],
|
|
1602
|
+
destructive: true
|
|
853
1603
|
},
|
|
854
1604
|
{
|
|
855
1605
|
id: "replay_webhook_delivery",
|
|
@@ -861,7 +1611,8 @@ var ACTIONS = [
|
|
|
861
1611
|
requests: [{ method: "POST", path: "/api/v1/webhook-deliveries/:id/replay" }],
|
|
862
1612
|
channels: ["mcp", "n8n"],
|
|
863
1613
|
idempotent: true,
|
|
864
|
-
guardrails: ["The receiving system will process the event again. Confirm it is idempotent first."]
|
|
1614
|
+
guardrails: ["The receiving system will process the event again. Confirm it is idempotent first."],
|
|
1615
|
+
destructive: true
|
|
865
1616
|
},
|
|
866
1617
|
{
|
|
867
1618
|
id: "send_test_webhook",
|
|
@@ -875,7 +1626,8 @@ var ACTIONS = [
|
|
|
875
1626
|
],
|
|
876
1627
|
requests: [{ method: "POST", path: "/api/v1/webhook-endpoints/:id/test" }],
|
|
877
1628
|
channels: ["mcp", "n8n"],
|
|
878
|
-
idempotent: true
|
|
1629
|
+
idempotent: true,
|
|
1630
|
+
destructive: true
|
|
879
1631
|
}
|
|
880
1632
|
];
|
|
881
1633
|
|
|
@@ -976,6 +1728,7 @@ var ThrottleError = class extends Error {
|
|
|
976
1728
|
statusCode;
|
|
977
1729
|
details;
|
|
978
1730
|
body;
|
|
1731
|
+
requestId;
|
|
979
1732
|
constructor(args) {
|
|
980
1733
|
super(args.message);
|
|
981
1734
|
this.name = "ThrottleError";
|
|
@@ -983,14 +1736,17 @@ var ThrottleError = class extends Error {
|
|
|
983
1736
|
this.statusCode = args.statusCode;
|
|
984
1737
|
this.details = args.details;
|
|
985
1738
|
this.body = args.body;
|
|
1739
|
+
const fromBody = args.body?.meta?.requestId;
|
|
1740
|
+
const fromHeader = typeof args.responseHeaders?.get === "function" ? args.responseHeaders.get("x-request-id") : args.responseHeaders?.["x-request-id"];
|
|
1741
|
+
this.requestId = args.requestId ?? (typeof fromBody === "string" ? fromBody : void 0) ?? (typeof fromHeader === "string" ? fromHeader : void 0);
|
|
986
1742
|
Object.setPrototypeOf(this, new.target.prototype);
|
|
987
1743
|
}
|
|
988
1744
|
};
|
|
989
1745
|
|
|
990
1746
|
// src/client.ts
|
|
991
1747
|
var ThrottleApiError = class extends ThrottleError {
|
|
992
|
-
constructor(code, message, statusCode, details) {
|
|
993
|
-
super({ code, message, statusCode, details });
|
|
1748
|
+
constructor(code, message, statusCode, details, responseHeaders) {
|
|
1749
|
+
super({ code, message, statusCode, details, responseHeaders });
|
|
994
1750
|
this.name = "ThrottleApiError";
|
|
995
1751
|
}
|
|
996
1752
|
};
|
|
@@ -1004,18 +1760,43 @@ function buildQuery(query) {
|
|
|
1004
1760
|
const qs = params.toString();
|
|
1005
1761
|
return qs ? `?${qs}` : "";
|
|
1006
1762
|
}
|
|
1763
|
+
var API_KEY_PREFIX_RE = /^(sk_|pk_)/;
|
|
1007
1764
|
var ThrottleClient = class {
|
|
1008
1765
|
constructor(auth, fetchImpl = fetch) {
|
|
1009
1766
|
this.auth = auth;
|
|
1010
1767
|
this.fetchImpl = fetchImpl;
|
|
1011
1768
|
}
|
|
1769
|
+
/**
|
|
1770
|
+
* Swap the credential every later request is sent with. The hosted remote
|
|
1771
|
+
* server builds one client per MCP session at `initialize`, but an OAuth
|
|
1772
|
+
* access token lives one hour and the MCP client refreshes it underneath
|
|
1773
|
+
* the session — each request then arrives with a newer bearer than the one
|
|
1774
|
+
* the session was opened with. Without this, the session keeps calling the
|
|
1775
|
+
* REST API with the token it was born with and every tool fails with a 401
|
|
1776
|
+
* the moment that first token expires, even though the caller is still
|
|
1777
|
+
* perfectly authenticated.
|
|
1778
|
+
*/
|
|
1779
|
+
setApiKey(apiKey) {
|
|
1780
|
+
this.auth = { ...this.auth, apiKey };
|
|
1781
|
+
}
|
|
1012
1782
|
async requestJson(method, path, options = {}) {
|
|
1013
1783
|
const url = `${this.auth.apiBaseUrl.replace(/\/$/, "")}${path}${buildQuery(options.query)}`;
|
|
1014
1784
|
const headers = {
|
|
1015
1785
|
"content-type": "application/json",
|
|
1016
|
-
"x-api-key": this.auth.apiKey
|
|
1786
|
+
"x-api-key": this.auth.apiKey,
|
|
1787
|
+
// Also sent as Authorization: Bearer — required for an OAuth access
|
|
1788
|
+
// token (the remote server's oauth_token sessions), which the auth
|
|
1789
|
+
// middleware only ever recognizes via this header (peeked by issuer),
|
|
1790
|
+
// never via x-api-key. For an sk_/pk_ key this is a no-op: the
|
|
1791
|
+
// middleware already treats `Authorization: Bearer sk_.../pk_...`
|
|
1792
|
+
// identically to `X-API-Key`, so existing stdio-CLI callers are
|
|
1793
|
+
// unaffected.
|
|
1794
|
+
Authorization: `Bearer ${this.auth.apiKey}`
|
|
1017
1795
|
};
|
|
1018
1796
|
if (options.idempotencyKey) headers["idempotency-key"] = options.idempotencyKey;
|
|
1797
|
+
if (options.applicationId && !API_KEY_PREFIX_RE.test(this.auth.apiKey)) {
|
|
1798
|
+
headers["x-throttle-application-id"] = options.applicationId;
|
|
1799
|
+
}
|
|
1019
1800
|
const sendsBody = method === "POST" || method === "PATCH" || method === "PUT";
|
|
1020
1801
|
const body = options.body !== void 0 ? JSON.stringify(options.body) : sendsBody ? "{}" : void 0;
|
|
1021
1802
|
const res = await this.fetchImpl(url, { method, headers, body });
|
|
@@ -1031,18 +1812,19 @@ var ThrottleClient = class {
|
|
|
1031
1812
|
err?.code ?? `http_${res.status}`,
|
|
1032
1813
|
err?.message ?? `Request failed (${res.status}): ${text.slice(0, 200)}`,
|
|
1033
1814
|
res.status,
|
|
1034
|
-
err?.details
|
|
1815
|
+
err?.details,
|
|
1816
|
+
res.headers
|
|
1035
1817
|
);
|
|
1036
1818
|
}
|
|
1037
1819
|
return parsed;
|
|
1038
1820
|
}
|
|
1039
1821
|
/** Unwraps the `{ data }` envelope. Key presence, not `??` — `{data: null}` unwraps to null. */
|
|
1040
|
-
async get(path, query) {
|
|
1041
|
-
const body = await this.requestJson("GET", path, { query });
|
|
1822
|
+
async get(path, query, options) {
|
|
1823
|
+
const body = await this.requestJson("GET", path, { query, applicationId: options?.applicationId });
|
|
1042
1824
|
return body && typeof body === "object" && "data" in body ? body.data : body;
|
|
1043
1825
|
}
|
|
1044
|
-
async list(path, query) {
|
|
1045
|
-
const body = await this.requestJson("GET", path, { query });
|
|
1826
|
+
async list(path, query, options) {
|
|
1827
|
+
const body = await this.requestJson("GET", path, { query, applicationId: options?.applicationId });
|
|
1046
1828
|
const data = "data" in body ? body.data : body;
|
|
1047
1829
|
const meta = body.meta;
|
|
1048
1830
|
return { data: Array.isArray(data) ? data : [], pagination: meta?.pagination ?? null };
|
|
@@ -1051,6 +1833,10 @@ var ThrottleClient = class {
|
|
|
1051
1833
|
const body = await this.requestJson("POST", path, options);
|
|
1052
1834
|
return body && typeof body === "object" && "data" in body ? body.data : body;
|
|
1053
1835
|
}
|
|
1836
|
+
async patch(path, options = {}) {
|
|
1837
|
+
const body = await this.requestJson("PATCH", path, options);
|
|
1838
|
+
return body && typeof body === "object" && "data" in body ? body.data : body;
|
|
1839
|
+
}
|
|
1054
1840
|
};
|
|
1055
1841
|
async function optional(load) {
|
|
1056
1842
|
try {
|
|
@@ -1199,7 +1985,7 @@ function validatePayload(index, input) {
|
|
|
1199
1985
|
}
|
|
1200
1986
|
|
|
1201
1987
|
// src/docs.ts
|
|
1202
|
-
var DOCS_ORIGIN = "https://
|
|
1988
|
+
var DOCS_ORIGIN = "https://usethrottle.dev/docs";
|
|
1203
1989
|
function excerptAround(text, needle, radius = 160) {
|
|
1204
1990
|
const at = text.toLowerCase().indexOf(needle.toLowerCase());
|
|
1205
1991
|
if (at < 0) return text.slice(0, radius * 2).trim();
|
|
@@ -1283,27 +2069,35 @@ var HANDLERS = {
|
|
|
1283
2069
|
// ------------------------------------------------------------- generic read
|
|
1284
2070
|
api_get: async (ctx, args) => {
|
|
1285
2071
|
const path = String(args.path ?? "");
|
|
1286
|
-
|
|
2072
|
+
const resolved = new URL(path, "https://throttle.invalid").pathname;
|
|
2073
|
+
if (!path.startsWith("/api/v1/") || !resolved.startsWith("/api/v1/") || /(^|[\/\\])\.(\.)?([\/\\?#]|$)|%2e/i.test(path)) {
|
|
1287
2074
|
throw new Error(
|
|
1288
2075
|
`Refusing to call "${path}". api_get only reads paths beginning with /api/v1/ on the configured Throttle host.`
|
|
1289
2076
|
);
|
|
1290
2077
|
}
|
|
1291
|
-
return ctx.client.get(path);
|
|
2078
|
+
return ctx.client.get(path, void 0, { applicationId: args.applicationId });
|
|
1292
2079
|
},
|
|
1293
2080
|
// ------------------------------------------------------------------ orders
|
|
1294
|
-
search_orders: async (ctx, args) => ctx.client.list(
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
2081
|
+
search_orders: async (ctx, args) => ctx.client.list(
|
|
2082
|
+
"/api/v1/orders",
|
|
2083
|
+
{
|
|
2084
|
+
q: args.q,
|
|
2085
|
+
status: args.status,
|
|
2086
|
+
paymentStatus: args.paymentStatus,
|
|
2087
|
+
createdAfter: args.createdAfter,
|
|
2088
|
+
createdBefore: args.createdBefore,
|
|
2089
|
+
...page(args)
|
|
2090
|
+
},
|
|
2091
|
+
{ applicationId: args.applicationId }
|
|
2092
|
+
),
|
|
1300
2093
|
get_order: async (ctx, args) => {
|
|
1301
2094
|
const id = String(args.id);
|
|
1302
|
-
const
|
|
2095
|
+
const opts = { applicationId: args.applicationId };
|
|
2096
|
+
const order = await ctx.client.get(`/api/v1/orders/${id}`, void 0, opts);
|
|
1303
2097
|
const [payments, returns, fulfillments] = await Promise.all([
|
|
1304
|
-
optional(() => ctx.client.list(`/api/v1/orders/${id}/payments
|
|
1305
|
-
optional(() => ctx.client.list(`/api/v1/orders/${id}/returns
|
|
1306
|
-
optional(() => ctx.client.list(`/api/v1/orders/${id}/fulfillments
|
|
2098
|
+
optional(() => ctx.client.list(`/api/v1/orders/${id}/payments`, void 0, opts)),
|
|
2099
|
+
optional(() => ctx.client.list(`/api/v1/orders/${id}/returns`, void 0, opts)),
|
|
2100
|
+
optional(() => ctx.client.list(`/api/v1/orders/${id}/fulfillments`, void 0, opts))
|
|
1307
2101
|
]);
|
|
1308
2102
|
return {
|
|
1309
2103
|
order,
|
|
@@ -1313,13 +2107,23 @@ var HANDLERS = {
|
|
|
1313
2107
|
};
|
|
1314
2108
|
},
|
|
1315
2109
|
// --------------------------------------------------------------- customers
|
|
1316
|
-
search_customers: async (ctx, args) => ctx.client.list(
|
|
2110
|
+
search_customers: async (ctx, args) => ctx.client.list(
|
|
2111
|
+
"/api/v1/customers",
|
|
2112
|
+
{
|
|
2113
|
+
q: args.q,
|
|
2114
|
+
createdAfter: args.createdAfter,
|
|
2115
|
+
createdBefore: args.createdBefore,
|
|
2116
|
+
...page(args)
|
|
2117
|
+
},
|
|
2118
|
+
{ applicationId: args.applicationId }
|
|
2119
|
+
),
|
|
1317
2120
|
get_customer: async (ctx, args) => {
|
|
1318
2121
|
const id = String(args.id);
|
|
1319
|
-
const
|
|
2122
|
+
const opts = { applicationId: args.applicationId };
|
|
2123
|
+
const customer = await ctx.client.get(`/api/v1/customers/${id}`, void 0, opts);
|
|
1320
2124
|
const [addresses, subscriptions] = await Promise.all([
|
|
1321
|
-
optional(() => ctx.client.list(`/api/v1/customers/${id}/addresses
|
|
1322
|
-
optional(() => ctx.client.list("/api/v1/subscriptions", { customerId: id }))
|
|
2125
|
+
optional(() => ctx.client.list(`/api/v1/customers/${id}/addresses`, void 0, opts)),
|
|
2126
|
+
optional(() => ctx.client.list("/api/v1/subscriptions", { customerId: id }, opts))
|
|
1323
2127
|
]);
|
|
1324
2128
|
return {
|
|
1325
2129
|
customer,
|
|
@@ -1328,60 +2132,101 @@ var HANDLERS = {
|
|
|
1328
2132
|
};
|
|
1329
2133
|
},
|
|
1330
2134
|
// ----------------------------------------------------------- subscriptions
|
|
1331
|
-
list_subscriptions: async (ctx, args) => ctx.client.list(
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
2135
|
+
list_subscriptions: async (ctx, args) => ctx.client.list(
|
|
2136
|
+
"/api/v1/subscriptions",
|
|
2137
|
+
{
|
|
2138
|
+
customerId: args.customerId,
|
|
2139
|
+
status: args.status,
|
|
2140
|
+
createdAfter: args.createdAfter,
|
|
2141
|
+
createdBefore: args.createdBefore,
|
|
2142
|
+
...page(args)
|
|
2143
|
+
},
|
|
2144
|
+
{ applicationId: args.applicationId }
|
|
2145
|
+
),
|
|
2146
|
+
get_subscription: async (ctx, args) => ctx.client.get(`/api/v1/subscriptions/${String(args.id)}`, void 0, {
|
|
2147
|
+
applicationId: args.applicationId
|
|
1335
2148
|
}),
|
|
1336
|
-
get_subscription: async (ctx, args) => ctx.client.get(`/api/v1/subscriptions/${String(args.id)}`),
|
|
1337
2149
|
// ------------------------------------------------------------------ quotes
|
|
1338
|
-
search_quotes: async (ctx, args) => ctx.client.list(
|
|
1339
|
-
|
|
2150
|
+
search_quotes: async (ctx, args) => ctx.client.list(
|
|
2151
|
+
"/api/v1/quotes",
|
|
2152
|
+
{
|
|
2153
|
+
status: args.status,
|
|
2154
|
+
createdAfter: args.createdAfter,
|
|
2155
|
+
createdBefore: args.createdBefore,
|
|
2156
|
+
...page(args)
|
|
2157
|
+
},
|
|
2158
|
+
{ applicationId: args.applicationId }
|
|
2159
|
+
),
|
|
2160
|
+
get_quote: async (ctx, args) => ctx.client.get(`/api/v1/quotes/${String(args.id)}`, void 0, {
|
|
2161
|
+
applicationId: args.applicationId
|
|
2162
|
+
}),
|
|
1340
2163
|
// ---------------------------------------------------------------- invoices
|
|
1341
|
-
list_invoices: async (ctx, args) => ctx.client.list(
|
|
1342
|
-
|
|
2164
|
+
list_invoices: async (ctx, args) => ctx.client.list(
|
|
2165
|
+
"/api/v1/invoices",
|
|
2166
|
+
{ status: args.status, ...page(args) },
|
|
2167
|
+
{ applicationId: args.applicationId }
|
|
2168
|
+
),
|
|
2169
|
+
receivables_aging: async (ctx, args) => ctx.client.get("/api/v1/invoices/aging", void 0, { applicationId: args.applicationId }),
|
|
1343
2170
|
// --------------------------------------------------------------- discounts
|
|
1344
|
-
search_discounts: async (ctx, args) => ctx.client.list(
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
2171
|
+
search_discounts: async (ctx, args) => ctx.client.list(
|
|
2172
|
+
"/api/v1/discounts",
|
|
2173
|
+
{
|
|
2174
|
+
q: args.q,
|
|
2175
|
+
code: args.code,
|
|
2176
|
+
type: args.type,
|
|
2177
|
+
isActive: args.isActive,
|
|
2178
|
+
createdAfter: args.createdAfter,
|
|
2179
|
+
createdBefore: args.createdBefore,
|
|
2180
|
+
...page(args)
|
|
2181
|
+
},
|
|
2182
|
+
{ applicationId: args.applicationId }
|
|
2183
|
+
),
|
|
2184
|
+
get_discount: async (ctx, args) => ctx.client.get(`/api/v1/discounts/${String(args.id)}`, void 0, {
|
|
2185
|
+
applicationId: args.applicationId
|
|
1352
2186
|
}),
|
|
1353
|
-
get_discount: async (ctx, args) => ctx.client.get(`/api/v1/discounts/${String(args.id)}`),
|
|
1354
2187
|
// ---------------------------------------------------------------- payments
|
|
1355
|
-
search_payments: async (ctx, args) => ctx.client.list(
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
2188
|
+
search_payments: async (ctx, args) => ctx.client.list(
|
|
2189
|
+
"/api/v1/payments",
|
|
2190
|
+
{
|
|
2191
|
+
method: args.method,
|
|
2192
|
+
status: args.status,
|
|
2193
|
+
// The route only accepts these as snake_case query params -- confirmed
|
|
2194
|
+
// by reading its querystring schema directly, not assumed from the
|
|
2195
|
+
// platform's usual camelCase convention.
|
|
2196
|
+
order_id: args.orderId,
|
|
2197
|
+
aging: args.aging,
|
|
2198
|
+
due_from: args.dueFrom,
|
|
2199
|
+
due_to: args.dueTo,
|
|
2200
|
+
amount_min: args.amountMin,
|
|
2201
|
+
amount_max: args.amountMax,
|
|
2202
|
+
...page(args)
|
|
2203
|
+
},
|
|
2204
|
+
{ applicationId: args.applicationId }
|
|
2205
|
+
),
|
|
1369
2206
|
get_payment: async (ctx, args) => {
|
|
1370
2207
|
const id = String(args.id);
|
|
1371
|
-
const
|
|
1372
|
-
const
|
|
2208
|
+
const opts = { applicationId: args.applicationId };
|
|
2209
|
+
const payment = await ctx.client.get(`/api/v1/payments/${id}`, void 0, opts);
|
|
2210
|
+
const transactions = await optional(
|
|
2211
|
+
() => ctx.client.list(`/api/v1/payments/${id}/transactions`, void 0, opts)
|
|
2212
|
+
);
|
|
1373
2213
|
return {
|
|
1374
2214
|
payment,
|
|
1375
2215
|
transactions: transactions.ok ? transactions.value.data : transactions.unavailable
|
|
1376
2216
|
};
|
|
1377
2217
|
},
|
|
1378
2218
|
// ---------------------------------------------------------------- invoices
|
|
1379
|
-
get_invoice: async (ctx, args) => ctx.client.get(`/api/v1/invoices/${String(args.id)}
|
|
2219
|
+
get_invoice: async (ctx, args) => ctx.client.get(`/api/v1/invoices/${String(args.id)}`, void 0, {
|
|
2220
|
+
applicationId: args.applicationId
|
|
2221
|
+
}),
|
|
1380
2222
|
// ------------------------------------------------------------------ carts
|
|
1381
2223
|
list_abandoned_carts: async (ctx, args) => {
|
|
2224
|
+
const opts = { applicationId: args.applicationId };
|
|
1382
2225
|
const [carts, summary] = await Promise.all([
|
|
1383
|
-
optional(() => ctx.client.list("/api/v1/abandoned-carts", { ...page(args) })),
|
|
1384
|
-
optional(
|
|
2226
|
+
optional(() => ctx.client.list("/api/v1/abandoned-carts", { ...page(args) }, opts)),
|
|
2227
|
+
optional(
|
|
2228
|
+
() => ctx.client.get("/api/v1/abandoned-carts/summary", { sinceDays: args.sinceDays }, opts)
|
|
2229
|
+
)
|
|
1385
2230
|
]);
|
|
1386
2231
|
return {
|
|
1387
2232
|
carts: carts.ok ? carts.value.data : carts.unavailable,
|
|
@@ -1389,18 +2234,29 @@ var HANDLERS = {
|
|
|
1389
2234
|
};
|
|
1390
2235
|
},
|
|
1391
2236
|
// --------------------------------------------------------------- debugging
|
|
1392
|
-
search_events: async (ctx, args) => ctx.client.list(
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
2237
|
+
search_events: async (ctx, args) => ctx.client.list(
|
|
2238
|
+
"/api/v1/events",
|
|
2239
|
+
{
|
|
2240
|
+
type: args.type,
|
|
2241
|
+
entityType: args.entityType,
|
|
2242
|
+
entityId: args.entityId,
|
|
2243
|
+
...page(args)
|
|
2244
|
+
},
|
|
2245
|
+
{ applicationId: args.applicationId }
|
|
2246
|
+
),
|
|
2247
|
+
list_webhook_deliveries: async (ctx, args) => ctx.client.list(
|
|
2248
|
+
"/api/v1/webhook-deliveries",
|
|
2249
|
+
{
|
|
2250
|
+
endpointId: args.endpointId,
|
|
2251
|
+
status: args.status,
|
|
2252
|
+
eventType: args.eventType,
|
|
2253
|
+
limit: args.limit
|
|
2254
|
+
},
|
|
2255
|
+
{ applicationId: args.applicationId }
|
|
2256
|
+
),
|
|
2257
|
+
get_webhook_delivery: async (ctx, args) => ctx.client.get(`/api/v1/webhook-deliveries/${String(args.id)}`, void 0, {
|
|
2258
|
+
applicationId: args.applicationId
|
|
1402
2259
|
}),
|
|
1403
|
-
get_webhook_delivery: async (ctx, args) => ctx.client.get(`/api/v1/webhook-deliveries/${String(args.id)}`),
|
|
1404
2260
|
send_test_webhook: async (ctx, args) => ctx.client.post(`/api/v1/webhook-endpoints/${String(args.id)}/test`, {
|
|
1405
2261
|
body: { eventType: args.eventType },
|
|
1406
2262
|
idempotencyKey: `mcp-send-test-webhook-${String(args.id)}-${Date.now()}`
|
|
@@ -1410,18 +2266,21 @@ var HANDLERS = {
|
|
|
1410
2266
|
}),
|
|
1411
2267
|
explain_checkout_failure: async (ctx, args) => {
|
|
1412
2268
|
const id = String(args.id);
|
|
1413
|
-
const
|
|
2269
|
+
const opts = { applicationId: args.applicationId };
|
|
2270
|
+
const session = await optional(
|
|
2271
|
+
() => ctx.client.get(`/api/v1/checkout-sessions/${id}`, void 0, opts)
|
|
2272
|
+
);
|
|
1414
2273
|
if (!session.ok) {
|
|
1415
2274
|
return {
|
|
1416
|
-
note: `No checkout session ${id} (${session.unavailable}). If this is a cart id,
|
|
2275
|
+
note: `No checkout session ${id} (${session.unavailable}). If this is a cart id, use search_events for its activity (or read /api/v1/carts/${id} with api_get, where that tool is available).`
|
|
1417
2276
|
};
|
|
1418
2277
|
}
|
|
1419
2278
|
const cartId = session.value?.cartId ?? void 0;
|
|
1420
2279
|
const [cart, sessionEvents, cartEvents, deliveries] = await Promise.all([
|
|
1421
|
-
optional(() => ctx.client.get(`/api/v1/checkout-sessions/${id}/cart
|
|
1422
|
-
optional(() => ctx.client.list("/api/v1/events", { entityId: id, limit: 25 })),
|
|
1423
|
-
cartId ? optional(() => ctx.client.list("/api/v1/events", { entityId: cartId, limit: 25 })) : Promise.resolve({ ok: true, value: { data: [], pagination: null } }),
|
|
1424
|
-
optional(() => ctx.client.list("/api/v1/webhook-deliveries", { limit: 15 }))
|
|
2280
|
+
optional(() => ctx.client.get(`/api/v1/checkout-sessions/${id}/cart`, void 0, opts)),
|
|
2281
|
+
optional(() => ctx.client.list("/api/v1/events", { entityId: id, limit: 25 }, opts)),
|
|
2282
|
+
cartId ? optional(() => ctx.client.list("/api/v1/events", { entityId: cartId, limit: 25 }, opts)) : Promise.resolve({ ok: true, value: { data: [], pagination: null } }),
|
|
2283
|
+
optional(() => ctx.client.list("/api/v1/webhook-deliveries", { limit: 15 }, opts))
|
|
1425
2284
|
]);
|
|
1426
2285
|
return {
|
|
1427
2286
|
session: session.value,
|
|
@@ -1481,6 +2340,73 @@ var HANDLERS = {
|
|
|
1481
2340
|
note: "Seeded a finalized DRAFT order. No payment was captured \u2014 that needs a browser-tokenised payment method, which a server-side key cannot produce."
|
|
1482
2341
|
};
|
|
1483
2342
|
},
|
|
2343
|
+
// ---------------------------------------------------------- inbound writes
|
|
2344
|
+
create_customer: async (ctx, args) => ctx.client.post("/api/v1/customers", {
|
|
2345
|
+
// `createCustomerBody` is `.strict()`, so only fields it declares may be
|
|
2346
|
+
// sent; `undefined` values drop out at JSON.stringify.
|
|
2347
|
+
body: {
|
|
2348
|
+
email: args.email,
|
|
2349
|
+
firstName: args.firstName,
|
|
2350
|
+
lastName: args.lastName,
|
|
2351
|
+
phone: args.phone,
|
|
2352
|
+
company: args.company,
|
|
2353
|
+
externalId: args.externalId,
|
|
2354
|
+
netN: args.netN,
|
|
2355
|
+
acceptsMarketing: args.acceptsMarketing
|
|
2356
|
+
},
|
|
2357
|
+
idempotencyKey: `mcp-create-customer-${String(args.email)}-${Date.now()}`
|
|
2358
|
+
}),
|
|
2359
|
+
update_customer: async (ctx, args) => ctx.client.patch(`/api/v1/customers/${String(args.id)}`, {
|
|
2360
|
+
// `id` is the path parameter, not a body field — `updateCustomerBody` is
|
|
2361
|
+
// `.strict()` and would reject it. A null `externalId` is meaningful
|
|
2362
|
+
// (it clears the value), so only `undefined` is dropped.
|
|
2363
|
+
body: {
|
|
2364
|
+
email: args.email,
|
|
2365
|
+
firstName: args.firstName,
|
|
2366
|
+
lastName: args.lastName,
|
|
2367
|
+
phone: args.phone,
|
|
2368
|
+
company: args.company,
|
|
2369
|
+
externalId: args.externalId,
|
|
2370
|
+
netN: args.netN,
|
|
2371
|
+
acceptsMarketing: args.acceptsMarketing
|
|
2372
|
+
},
|
|
2373
|
+
idempotencyKey: `mcp-update-customer-${String(args.id)}-${Date.now()}`
|
|
2374
|
+
}),
|
|
2375
|
+
create_checkout_link: async (ctx, args) => {
|
|
2376
|
+
const lineItems = Array.isArray(args.lineItems) ? args.lineItems : [];
|
|
2377
|
+
if (lineItems.length === 0) {
|
|
2378
|
+
throw new Error("create_checkout_link: lineItems must contain at least one item.");
|
|
2379
|
+
}
|
|
2380
|
+
const stamp = Date.now();
|
|
2381
|
+
const applicationId = ctx.identity.application?.id;
|
|
2382
|
+
const cart = await ctx.client.post("/api/v1/carts", {
|
|
2383
|
+
body: {
|
|
2384
|
+
applicationId,
|
|
2385
|
+
customerId: args.customerId,
|
|
2386
|
+
customerEmail: args.customerEmail,
|
|
2387
|
+
netN: args.netN
|
|
2388
|
+
},
|
|
2389
|
+
idempotencyKey: `mcp-checkout-link-cart-${stamp}`
|
|
2390
|
+
});
|
|
2391
|
+
for (const [index, item] of lineItems.entries()) {
|
|
2392
|
+
await ctx.client.post(`/api/v1/carts/${cart?.id}/items`, {
|
|
2393
|
+
body: item,
|
|
2394
|
+
idempotencyKey: `mcp-checkout-link-item-${stamp}-${index}`
|
|
2395
|
+
});
|
|
2396
|
+
}
|
|
2397
|
+
const session = await ctx.client.post("/api/v1/checkout/sessions", {
|
|
2398
|
+
body: {
|
|
2399
|
+
applicationId,
|
|
2400
|
+
cartId: cart?.id,
|
|
2401
|
+
returnUrl: args.returnUrl,
|
|
2402
|
+
cancelUrl: args.cancelUrl,
|
|
2403
|
+
customerEmail: args.customerEmail,
|
|
2404
|
+
discountCode: args.discountCode
|
|
2405
|
+
},
|
|
2406
|
+
idempotencyKey: `mcp-checkout-link-session-${stamp}`
|
|
2407
|
+
});
|
|
2408
|
+
return { cartId: cart?.id, ...session };
|
|
2409
|
+
},
|
|
1484
2410
|
// -------------------------------------------------------------- money/mail
|
|
1485
2411
|
manage_subscription: async (ctx, args) => {
|
|
1486
2412
|
const id = String(args.id);
|
|
@@ -1510,17 +2436,118 @@ var HANDLERS = {
|
|
|
1510
2436
|
idempotencyKey: `mcp-create-quote-${String(args.customerId)}-${Date.now()}`
|
|
1511
2437
|
})
|
|
1512
2438
|
),
|
|
1513
|
-
|
|
1514
|
-
|
|
2439
|
+
get_sales_timeseries: async (ctx, args) => ctx.client.get(
|
|
2440
|
+
"/api/v1/analytics/orders-timeseries",
|
|
2441
|
+
{
|
|
2442
|
+
from: args.from,
|
|
2443
|
+
to: args.to,
|
|
2444
|
+
groupBy: args.groupBy,
|
|
2445
|
+
timezone: args.timezone,
|
|
2446
|
+
compareTo: args.compareTo
|
|
2447
|
+
},
|
|
2448
|
+
{ applicationId: args.applicationId }
|
|
2449
|
+
),
|
|
2450
|
+
get_business_summary: async (ctx, args) => ctx.client.get(
|
|
2451
|
+
"/api/v1/analytics/business-summary",
|
|
2452
|
+
{
|
|
2453
|
+
period: args.period,
|
|
2454
|
+
from: args.from,
|
|
2455
|
+
to: args.to,
|
|
2456
|
+
timezone: args.timezone
|
|
2457
|
+
},
|
|
2458
|
+
{ applicationId: args.applicationId }
|
|
2459
|
+
),
|
|
2460
|
+
get_attention_digest: async (ctx, args) => ctx.client.get("/api/v1/analytics/attention-digest", void 0, {
|
|
2461
|
+
applicationId: args.applicationId
|
|
2462
|
+
}),
|
|
2463
|
+
get_top_products: async (ctx, args) => ctx.client.get(
|
|
2464
|
+
"/api/v1/analytics/products",
|
|
2465
|
+
{
|
|
2466
|
+
from: args.from,
|
|
2467
|
+
to: args.to,
|
|
2468
|
+
timezone: args.timezone,
|
|
2469
|
+
by: args.by,
|
|
2470
|
+
limit: args.limit
|
|
2471
|
+
},
|
|
2472
|
+
{ applicationId: args.applicationId }
|
|
2473
|
+
),
|
|
2474
|
+
// `.get()`, not `.list()` — the response is a single object (`{ data: {
|
|
2475
|
+
// range, signups, ordersByCustomerType, topCustomers, totals } }`), and
|
|
2476
|
+
// `.list()` only unwraps arrays: it would silently return an empty list
|
|
2477
|
+
// instead of the real payload.
|
|
2478
|
+
get_customer_growth: async (ctx, args) => ctx.client.get(
|
|
2479
|
+
"/api/v1/analytics/customers",
|
|
2480
|
+
{
|
|
2481
|
+
from: args.from,
|
|
2482
|
+
to: args.to,
|
|
2483
|
+
groupBy: args.groupBy,
|
|
2484
|
+
timezone: args.timezone,
|
|
2485
|
+
topBy: args.topBy,
|
|
2486
|
+
topLimit: args.topLimit
|
|
2487
|
+
},
|
|
2488
|
+
{ applicationId: args.applicationId }
|
|
2489
|
+
),
|
|
2490
|
+
// `.get()`, not `.list()` — the response is a single object (`{ data: {
|
|
2491
|
+
// range, dimension, rows, totals, note } }`), not a paginated array.
|
|
2492
|
+
get_revenue_breakdown: async (ctx, args) => ctx.client.get(
|
|
2493
|
+
"/api/v1/analytics/revenue-breakdown",
|
|
2494
|
+
{
|
|
2495
|
+
dimension: args.dimension,
|
|
2496
|
+
from: args.from,
|
|
2497
|
+
to: args.to,
|
|
2498
|
+
timezone: args.timezone
|
|
2499
|
+
},
|
|
2500
|
+
{ applicationId: args.applicationId }
|
|
2501
|
+
),
|
|
2502
|
+
// `.get()`, not `.list()` — the response is a single object (`{ data: {
|
|
2503
|
+
// range, stages, conversionRate, dropOff, note } }`), not a paginated array.
|
|
2504
|
+
get_funnel_conversion: async (ctx, args) => ctx.client.get(
|
|
2505
|
+
"/api/v1/analytics/funnel",
|
|
2506
|
+
{
|
|
2507
|
+
from: args.from,
|
|
2508
|
+
to: args.to,
|
|
2509
|
+
timezone: args.timezone
|
|
2510
|
+
},
|
|
2511
|
+
{ applicationId: args.applicationId }
|
|
2512
|
+
),
|
|
2513
|
+
// `.get()`, not `.list()` — the response is a single object (`{ data: {
|
|
2514
|
+
// range, series, totals, note } }`), not a paginated array.
|
|
2515
|
+
get_subscription_movement: async (ctx, args) => ctx.client.get(
|
|
2516
|
+
"/api/v1/analytics/subscriptions",
|
|
2517
|
+
{
|
|
2518
|
+
from: args.from,
|
|
2519
|
+
to: args.to,
|
|
2520
|
+
timezone: args.timezone,
|
|
2521
|
+
groupBy: args.groupBy
|
|
2522
|
+
},
|
|
2523
|
+
{ applicationId: args.applicationId }
|
|
2524
|
+
),
|
|
2525
|
+
// `.get()`, not `.list()` — the response is a single object (`{ data: {
|
|
2526
|
+
// range, rows, totals, note } }`), not a paginated array.
|
|
2527
|
+
get_discount_performance: async (ctx, args) => ctx.client.get(
|
|
2528
|
+
"/api/v1/analytics/discounts",
|
|
2529
|
+
{
|
|
2530
|
+
from: args.from,
|
|
2531
|
+
to: args.to,
|
|
2532
|
+
timezone: args.timezone,
|
|
2533
|
+
limit: args.limit
|
|
2534
|
+
},
|
|
2535
|
+
{ applicationId: args.applicationId }
|
|
2536
|
+
),
|
|
1515
2537
|
// ------------------------------------------------------------- extensions
|
|
1516
|
-
list_extension_installations: async (ctx, args) => ctx.client.list(
|
|
2538
|
+
list_extension_installations: async (ctx, args) => ctx.client.list(
|
|
2539
|
+
"/api/v1/installations",
|
|
2540
|
+
{ status: args.status, ...page(args) },
|
|
2541
|
+
{ applicationId: args.applicationId }
|
|
2542
|
+
),
|
|
1517
2543
|
// ------------------------------------------------------------- connectors
|
|
1518
|
-
get_connector_routing: async (ctx) => {
|
|
2544
|
+
get_connector_routing: async (ctx, args) => {
|
|
2545
|
+
const opts = { applicationId: args.applicationId };
|
|
1519
2546
|
const [paymentRouting, routingConnectors, cardDecline, otherDecline] = await Promise.all([
|
|
1520
|
-
optional(() => ctx.client.get("/api/v1/payment-routing")),
|
|
1521
|
-
optional(() => ctx.client.get("/api/v1/routing-connectors")),
|
|
1522
|
-
optional(() => ctx.client.get("/api/v1/card-decline")),
|
|
1523
|
-
optional(() => ctx.client.get("/api/v1/other-decline"))
|
|
2547
|
+
optional(() => ctx.client.get("/api/v1/payment-routing", void 0, opts)),
|
|
2548
|
+
optional(() => ctx.client.get("/api/v1/routing-connectors", void 0, opts)),
|
|
2549
|
+
optional(() => ctx.client.get("/api/v1/card-decline", void 0, opts)),
|
|
2550
|
+
optional(() => ctx.client.get("/api/v1/other-decline", void 0, opts))
|
|
1524
2551
|
]);
|
|
1525
2552
|
return {
|
|
1526
2553
|
paymentRouting: paymentRouting.ok ? paymentRouting.value : paymentRouting.unavailable,
|
|
@@ -1529,24 +2556,37 @@ var HANDLERS = {
|
|
|
1529
2556
|
otherDecline: otherDecline.ok ? otherDecline.value : otherDecline.unavailable
|
|
1530
2557
|
};
|
|
1531
2558
|
},
|
|
1532
|
-
get_integration_status: async (ctx) => {
|
|
2559
|
+
get_integration_status: async (ctx, args) => {
|
|
2560
|
+
const opts = { applicationId: args.applicationId };
|
|
1533
2561
|
const [connectors, endpoints, coverage] = await Promise.all([
|
|
1534
|
-
optional(() => ctx.client.list("/api/v1/connectors")),
|
|
1535
|
-
optional(() => ctx.client.list("/api/v1/webhook-endpoints")),
|
|
1536
|
-
optional(() => ctx.client.get("/api/v1/webhook-endpoints/coverage"))
|
|
2562
|
+
optional(() => ctx.client.list("/api/v1/connectors", void 0, opts)),
|
|
2563
|
+
optional(() => ctx.client.list("/api/v1/webhook-endpoints", void 0, opts)),
|
|
2564
|
+
optional(() => ctx.client.get("/api/v1/webhook-endpoints/coverage", void 0, opts))
|
|
1537
2565
|
]);
|
|
2566
|
+
const overridden = Boolean(args.applicationId) && args.applicationId !== ctx.identity.application?.id;
|
|
1538
2567
|
return {
|
|
1539
2568
|
environment: ctx.identity.environment,
|
|
1540
2569
|
workspace: ctx.identity.workspace,
|
|
1541
|
-
|
|
2570
|
+
applicationId: args.applicationId ?? ctx.identity.application?.id ?? null,
|
|
2571
|
+
application: overridden ? void 0 : ctx.identity.application,
|
|
1542
2572
|
connectors: connectors.ok ? connectors.value.data : connectors.unavailable,
|
|
1543
2573
|
webhookEndpoints: endpoints.ok ? endpoints.value.data : endpoints.unavailable,
|
|
1544
2574
|
unsubscribedEvents: coverage.ok ? coverage.value : coverage.unavailable
|
|
1545
2575
|
};
|
|
1546
2576
|
},
|
|
1547
2577
|
// --------------------------------------------------------------- workspace
|
|
2578
|
+
// No applicationId forwarding here: /api/v1/merchants/me is workspace-only
|
|
2579
|
+
// (`WHERE workspace_id = ...`, never reads auth.applicationId) — an
|
|
2580
|
+
// applicationId field on this tool would be a silent no-op affordance.
|
|
1548
2581
|
get_workspace_settings: async (ctx) => ctx.client.get("/api/v1/merchants/me"),
|
|
1549
|
-
|
|
2582
|
+
// No applicationId forwarding here either: /api/v1/audit-log filters by
|
|
2583
|
+
// workspaceId (and optionally environmentId), never applicationId.
|
|
2584
|
+
get_audit_log: async (ctx, args) => ctx.client.list("/api/v1/audit-log", { limit: args.limit }),
|
|
2585
|
+
// --------------------------------------------------------- applications
|
|
2586
|
+
// `.get()`, not `.list()` — the legacy /v1/stores route returns a bare
|
|
2587
|
+
// array with no `meta.pagination`, and the brief calls for `.get()` here
|
|
2588
|
+
// specifically.
|
|
2589
|
+
list_applications: async (ctx) => ctx.client.get("/api/v1/stores")
|
|
1550
2590
|
};
|
|
1551
2591
|
|
|
1552
2592
|
// src/input.ts
|
|
@@ -1581,15 +2621,548 @@ function applyModifiers(field, type) {
|
|
|
1581
2621
|
return field.default === void 0 ? described.optional() : described.default(field.default);
|
|
1582
2622
|
}
|
|
1583
2623
|
|
|
2624
|
+
// src/projection.ts
|
|
2625
|
+
var S = "scalar";
|
|
2626
|
+
var T = "text";
|
|
2627
|
+
var PASS = "pass";
|
|
2628
|
+
var section = (shape) => ({ __section: shape });
|
|
2629
|
+
var oneOf = (values) => ({ __oneOf: new Set(values) });
|
|
2630
|
+
function isPrimitive(value) {
|
|
2631
|
+
return value === null || ["string", "number", "boolean"].includes(typeof value);
|
|
2632
|
+
}
|
|
2633
|
+
var TOKEN = /^[^\s@]{0,200}$/;
|
|
2634
|
+
var OMITTED = { omitted: S, grantable: T, reason: T };
|
|
2635
|
+
var UNAVAILABLE = /^(not readable with this credential \(missing scope\)|not found|unavailable \([^\s@]{0,100}\))$/;
|
|
2636
|
+
function project(value, shape) {
|
|
2637
|
+
if (value === void 0) return void 0;
|
|
2638
|
+
if (shape === PASS) return value;
|
|
2639
|
+
if (shape === S) {
|
|
2640
|
+
if (!isPrimitive(value)) return void 0;
|
|
2641
|
+
return typeof value === "string" && !TOKEN.test(value) ? null : value;
|
|
2642
|
+
}
|
|
2643
|
+
if (shape === T) return isPrimitive(value) ? value : void 0;
|
|
2644
|
+
if ("__oneOf" in shape) {
|
|
2645
|
+
if (!isPrimitive(value)) return void 0;
|
|
2646
|
+
return typeof value === "string" && !shape.__oneOf.has(value) ? null : value;
|
|
2647
|
+
}
|
|
2648
|
+
if (Array.isArray(shape)) {
|
|
2649
|
+
return Array.isArray(value) ? value.map((item) => project(item, shape[0])) : void 0;
|
|
2650
|
+
}
|
|
2651
|
+
if ("__section" in shape) {
|
|
2652
|
+
if (typeof value === "string") return UNAVAILABLE.test(value) ? value : null;
|
|
2653
|
+
if (value && typeof value === "object" && value.omitted === true) {
|
|
2654
|
+
return project(value, OMITTED);
|
|
2655
|
+
}
|
|
2656
|
+
return project(value, shape.__section);
|
|
2657
|
+
}
|
|
2658
|
+
if (value === null) return null;
|
|
2659
|
+
if (typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
2660
|
+
const out = {};
|
|
2661
|
+
for (const [key, child] of Object.entries(shape)) {
|
|
2662
|
+
const projected = project(value[key], child);
|
|
2663
|
+
if (projected !== void 0) out[key] = projected;
|
|
2664
|
+
}
|
|
2665
|
+
return out;
|
|
2666
|
+
}
|
|
2667
|
+
var PAGINATION = { cursor: S, nextCursor: S, hasMore: S };
|
|
2668
|
+
var page2 = (row) => ({ data: [row], pagination: PAGINATION });
|
|
2669
|
+
var ADDRESS = { countryCode: S };
|
|
2670
|
+
var CUSTOMER_REF = { id: S };
|
|
2671
|
+
var APPLICATION = { id: S, name: T, slug: S };
|
|
2672
|
+
var LIFECYCLE_LABELS = ["Draft", "Pending", "Processing", "Partially fulfilled", "Fulfilled", "Cancelled", "Closed"];
|
|
2673
|
+
var DISPLAY_QUALIFIERS = [
|
|
2674
|
+
"Comped",
|
|
2675
|
+
"Awaiting payment",
|
|
2676
|
+
"Authorized",
|
|
2677
|
+
"Partially paid",
|
|
2678
|
+
"Partially refunded",
|
|
2679
|
+
"Refunded",
|
|
2680
|
+
"Payment failed",
|
|
2681
|
+
"Voided",
|
|
2682
|
+
"Payment processing",
|
|
2683
|
+
"Disputed",
|
|
2684
|
+
"Authorization expired",
|
|
2685
|
+
"Returned",
|
|
2686
|
+
"Partially returned",
|
|
2687
|
+
"Delivered",
|
|
2688
|
+
"In transit"
|
|
2689
|
+
];
|
|
2690
|
+
var ORDER_DISPLAY_STATUSES = new Set(
|
|
2691
|
+
LIFECYCLE_LABELS.flatMap((l) => [l, ...DISPLAY_QUALIFIERS.map((q) => `${l} \xB7 ${q}`)])
|
|
2692
|
+
);
|
|
2693
|
+
var DISPLAY_STATUS = oneOf(ORDER_DISPLAY_STATUSES);
|
|
2694
|
+
var RANGE = { from: S, to: S, timezone: S, groupBy: S };
|
|
2695
|
+
var LINE_ITEM = {
|
|
2696
|
+
id: S,
|
|
2697
|
+
type: S,
|
|
2698
|
+
quantity: S,
|
|
2699
|
+
unitPrice: S,
|
|
2700
|
+
subtotal: S,
|
|
2701
|
+
discountAmount: S,
|
|
2702
|
+
taxAmount: S,
|
|
2703
|
+
total: S,
|
|
2704
|
+
recurring: S
|
|
2705
|
+
};
|
|
2706
|
+
var ORDER = {
|
|
2707
|
+
id: S,
|
|
2708
|
+
orderNumber: S,
|
|
2709
|
+
status: S,
|
|
2710
|
+
displayStatus: DISPLAY_STATUS,
|
|
2711
|
+
paymentStatus: S,
|
|
2712
|
+
type: S,
|
|
2713
|
+
source: S,
|
|
2714
|
+
currency: S,
|
|
2715
|
+
subtotal: S,
|
|
2716
|
+
discountTotal: S,
|
|
2717
|
+
shippingTotal: S,
|
|
2718
|
+
taxTotal: S,
|
|
2719
|
+
total: S,
|
|
2720
|
+
capturedAmount: S,
|
|
2721
|
+
refundedAmount: S,
|
|
2722
|
+
compAmount: S,
|
|
2723
|
+
onHold: S,
|
|
2724
|
+
taxReverseCharge: S,
|
|
2725
|
+
customerId: S,
|
|
2726
|
+
customer: CUSTOMER_REF,
|
|
2727
|
+
subscriptionId: S,
|
|
2728
|
+
shippingAddress: ADDRESS,
|
|
2729
|
+
billingAddress: ADDRESS,
|
|
2730
|
+
fulfillmentSummary: { itemsFulfilled: S, itemsRemaining: S, itemsTotal: S },
|
|
2731
|
+
lineItems: [LINE_ITEM],
|
|
2732
|
+
createdAt: S,
|
|
2733
|
+
completedAt: S,
|
|
2734
|
+
cancelledAt: S,
|
|
2735
|
+
closedAt: S
|
|
2736
|
+
};
|
|
2737
|
+
var PAYMENT = {
|
|
2738
|
+
id: S,
|
|
2739
|
+
orderId: S,
|
|
2740
|
+
status: S,
|
|
2741
|
+
method: S,
|
|
2742
|
+
processor: S,
|
|
2743
|
+
amount: S,
|
|
2744
|
+
capturedAmount: S,
|
|
2745
|
+
currency: S,
|
|
2746
|
+
cardBrand: S,
|
|
2747
|
+
disputed: S,
|
|
2748
|
+
disputeOpenedAt: S,
|
|
2749
|
+
errorCode: S,
|
|
2750
|
+
netN: S,
|
|
2751
|
+
netNSource: S,
|
|
2752
|
+
authorizedAt: S,
|
|
2753
|
+
capturedAt: S,
|
|
2754
|
+
voidedAt: S,
|
|
2755
|
+
createdAt: S
|
|
2756
|
+
};
|
|
2757
|
+
var RETURN = {
|
|
2758
|
+
id: S,
|
|
2759
|
+
orderId: S,
|
|
2760
|
+
status: S,
|
|
2761
|
+
restock: S,
|
|
2762
|
+
refundAmount: S,
|
|
2763
|
+
refundPaymentId: S,
|
|
2764
|
+
createdAt: S,
|
|
2765
|
+
items: [{ lineItemId: S, quantity: S }]
|
|
2766
|
+
};
|
|
2767
|
+
var FULFILLMENT = {
|
|
2768
|
+
id: S,
|
|
2769
|
+
orderId: S,
|
|
2770
|
+
type: S,
|
|
2771
|
+
status: S,
|
|
2772
|
+
createdAt: S,
|
|
2773
|
+
lineItems: [{ lineItemId: S, quantity: S }],
|
|
2774
|
+
detail: { carrier: T, serviceLevel: T, trackingNumber: T, trackingUrl: T, shippedAt: S, estimatedDelivery: S, actualDelivery: S }
|
|
2775
|
+
};
|
|
2776
|
+
var CUSTOMER = { id: S, netN: S, acceptsMarketing: S, createdAt: S };
|
|
2777
|
+
var SUBSCRIPTION = {
|
|
2778
|
+
id: S,
|
|
2779
|
+
status: S,
|
|
2780
|
+
planName: T,
|
|
2781
|
+
planReference: T,
|
|
2782
|
+
amount: S,
|
|
2783
|
+
currency: S,
|
|
2784
|
+
interval: S,
|
|
2785
|
+
quantity: S,
|
|
2786
|
+
intervalUnit: S,
|
|
2787
|
+
intervalCount: S,
|
|
2788
|
+
totalPayments: S,
|
|
2789
|
+
paymentsMade: S,
|
|
2790
|
+
completedAt: S,
|
|
2791
|
+
customerId: S,
|
|
2792
|
+
customer: CUSTOMER_REF,
|
|
2793
|
+
currentPeriodStart: S,
|
|
2794
|
+
currentPeriodEnd: S,
|
|
2795
|
+
billingAnchorAt: S,
|
|
2796
|
+
cancelAtPeriodEnd: S,
|
|
2797
|
+
trialEnd: S,
|
|
2798
|
+
pausedAt: S,
|
|
2799
|
+
cancelledAt: S,
|
|
2800
|
+
lastPaymentAt: S,
|
|
2801
|
+
failureCount: S,
|
|
2802
|
+
pendingPlanName: T,
|
|
2803
|
+
pendingPlanReference: T,
|
|
2804
|
+
pendingAmount: S,
|
|
2805
|
+
pendingInterval: S,
|
|
2806
|
+
pendingQuantity: S,
|
|
2807
|
+
pendingIntervalUnit: S,
|
|
2808
|
+
pendingIntervalCount: S,
|
|
2809
|
+
createdAt: S
|
|
2810
|
+
};
|
|
2811
|
+
var QUOTE_SUMMARY = {
|
|
2812
|
+
id: S,
|
|
2813
|
+
quoteNumber: S,
|
|
2814
|
+
status: S,
|
|
2815
|
+
source: S,
|
|
2816
|
+
currency: S,
|
|
2817
|
+
total: S,
|
|
2818
|
+
customerId: S,
|
|
2819
|
+
orderId: S,
|
|
2820
|
+
expiresAt: S,
|
|
2821
|
+
acceptedAt: S,
|
|
2822
|
+
declinedAt: S,
|
|
2823
|
+
createdAt: S
|
|
2824
|
+
};
|
|
2825
|
+
var QUOTE = {
|
|
2826
|
+
...QUOTE_SUMMARY,
|
|
2827
|
+
revisions: [{
|
|
2828
|
+
id: S,
|
|
2829
|
+
revisionNumber: S,
|
|
2830
|
+
state: S,
|
|
2831
|
+
subtotal: S,
|
|
2832
|
+
discountTotal: S,
|
|
2833
|
+
shippingTotal: S,
|
|
2834
|
+
taxTotal: S,
|
|
2835
|
+
total: S,
|
|
2836
|
+
depositAmount: S,
|
|
2837
|
+
shippingMode: S,
|
|
2838
|
+
issuedAt: S,
|
|
2839
|
+
expiresAt: S,
|
|
2840
|
+
paymentTerms: { mode: S, netN: S, balanceNetN: S, deposit: { type: S, value: S } },
|
|
2841
|
+
items: [{
|
|
2842
|
+
type: S,
|
|
2843
|
+
quantity: S,
|
|
2844
|
+
unitPrice: S,
|
|
2845
|
+
subtotal: S,
|
|
2846
|
+
discountAmount: S,
|
|
2847
|
+
taxAmount: S,
|
|
2848
|
+
total: S,
|
|
2849
|
+
optional: S,
|
|
2850
|
+
optionalSelected: S
|
|
2851
|
+
}]
|
|
2852
|
+
}],
|
|
2853
|
+
// Who commented and when, never what they wrote.
|
|
2854
|
+
comments: [{ authorType: S, visibility: S, createdAt: S }],
|
|
2855
|
+
timeline: [{ type: S, actorType: S, createdAt: S }]
|
|
2856
|
+
};
|
|
2857
|
+
var INVOICE = {
|
|
2858
|
+
id: S,
|
|
2859
|
+
number: S,
|
|
2860
|
+
status: S,
|
|
2861
|
+
sourceType: S,
|
|
2862
|
+
sourceId: S,
|
|
2863
|
+
customerId: S,
|
|
2864
|
+
currency: S,
|
|
2865
|
+
subtotal: S,
|
|
2866
|
+
discountTotal: S,
|
|
2867
|
+
shippingTotal: S,
|
|
2868
|
+
taxTotal: S,
|
|
2869
|
+
total: S,
|
|
2870
|
+
amountRefunded: S,
|
|
2871
|
+
issuedAt: S,
|
|
2872
|
+
dueAt: S,
|
|
2873
|
+
periodStart: S,
|
|
2874
|
+
periodEnd: S,
|
|
2875
|
+
createdAt: S,
|
|
2876
|
+
billingSnapshot: {
|
|
2877
|
+
planName: T,
|
|
2878
|
+
period: { start: S, end: S },
|
|
2879
|
+
lineItems: [{ quantity: S, unitPrice: S, discountAmount: S, taxAmount: S, total: S }],
|
|
2880
|
+
totals: { currency: S, subtotal: S, discountTotal: S, shippingTotal: S, taxTotal: S, total: S },
|
|
2881
|
+
payment: { method: S, brand: S, status: S }
|
|
2882
|
+
}
|
|
2883
|
+
};
|
|
2884
|
+
var DISCOUNT = {
|
|
2885
|
+
id: S,
|
|
2886
|
+
code: T,
|
|
2887
|
+
name: T,
|
|
2888
|
+
type: S,
|
|
2889
|
+
value: S,
|
|
2890
|
+
isActive: S,
|
|
2891
|
+
startsAt: S,
|
|
2892
|
+
endsAt: S,
|
|
2893
|
+
usageCount: S,
|
|
2894
|
+
usageLimit: S,
|
|
2895
|
+
maxRedemptionsPerCustomer: S,
|
|
2896
|
+
createdAt: S
|
|
2897
|
+
};
|
|
2898
|
+
var EVENT = {
|
|
2899
|
+
id: S,
|
|
2900
|
+
type: S,
|
|
2901
|
+
entityType: S,
|
|
2902
|
+
entityId: S,
|
|
2903
|
+
createdAt: S,
|
|
2904
|
+
data: {
|
|
2905
|
+
status: S,
|
|
2906
|
+
previousStatus: S,
|
|
2907
|
+
action: S,
|
|
2908
|
+
amount: S,
|
|
2909
|
+
currency: S,
|
|
2910
|
+
attempt: S,
|
|
2911
|
+
orderId: S,
|
|
2912
|
+
paymentId: S,
|
|
2913
|
+
order: { id: S, orderNumber: S, status: S, paymentStatus: S, currency: S, total: S },
|
|
2914
|
+
payment: { id: S, orderId: S, status: S, method: S, amount: S, currency: S },
|
|
2915
|
+
subscription: { id: S, status: S, planName: T, amount: S, currency: S, interval: S },
|
|
2916
|
+
fulfillment: { id: S, orderId: S, type: S, status: S }
|
|
2917
|
+
}
|
|
2918
|
+
};
|
|
2919
|
+
var DELIVERY = {
|
|
2920
|
+
id: S,
|
|
2921
|
+
endpointId: S,
|
|
2922
|
+
eventId: S,
|
|
2923
|
+
eventType: S,
|
|
2924
|
+
status: S,
|
|
2925
|
+
attemptCount: S,
|
|
2926
|
+
lastStatus: S,
|
|
2927
|
+
deliveredAt: S,
|
|
2928
|
+
nextRetryAt: S,
|
|
2929
|
+
createdAt: S,
|
|
2930
|
+
replayOf: S
|
|
2931
|
+
};
|
|
2932
|
+
var MONEY_COUNT = { count: S, totalCents: S };
|
|
2933
|
+
var ORDER_PERIOD = { orderCount: S, grossCents: S, netCents: S, refundsCents: S, avgOrderCents: S };
|
|
2934
|
+
var OAUTH_PROJECTIONS = {
|
|
2935
|
+
whoami: {
|
|
2936
|
+
type: S,
|
|
2937
|
+
workspace: { name: T, slug: S },
|
|
2938
|
+
application: APPLICATION,
|
|
2939
|
+
environment: { slug: S, kind: S, providerEnvironment: S },
|
|
2940
|
+
note: T
|
|
2941
|
+
},
|
|
2942
|
+
search_docs: PASS,
|
|
2943
|
+
describe_endpoint: PASS,
|
|
2944
|
+
validate_payload: PASS,
|
|
2945
|
+
search_orders: page2(ORDER),
|
|
2946
|
+
get_order: {
|
|
2947
|
+
order: ORDER,
|
|
2948
|
+
payments: section([PAYMENT]),
|
|
2949
|
+
returns: section([RETURN]),
|
|
2950
|
+
fulfillments: section([FULFILLMENT])
|
|
2951
|
+
},
|
|
2952
|
+
search_customers: page2(CUSTOMER),
|
|
2953
|
+
get_customer: {
|
|
2954
|
+
customer: CUSTOMER,
|
|
2955
|
+
addresses: section([{ id: S, isDefault: S, ...ADDRESS }]),
|
|
2956
|
+
subscriptions: section([SUBSCRIPTION])
|
|
2957
|
+
},
|
|
2958
|
+
list_subscriptions: page2(SUBSCRIPTION),
|
|
2959
|
+
get_subscription: SUBSCRIPTION,
|
|
2960
|
+
search_quotes: page2(QUOTE_SUMMARY),
|
|
2961
|
+
get_quote: QUOTE,
|
|
2962
|
+
list_invoices: page2(INVOICE),
|
|
2963
|
+
get_invoice: INVOICE,
|
|
2964
|
+
receivables_aging: {
|
|
2965
|
+
buckets: [{ key: S, label: T, count: S, total_amount: S }],
|
|
2966
|
+
total_outstanding_amount: S,
|
|
2967
|
+
total_outstanding_count: S
|
|
2968
|
+
},
|
|
2969
|
+
search_discounts: page2(DISCOUNT),
|
|
2970
|
+
get_discount: DISCOUNT,
|
|
2971
|
+
search_payments: page2(PAYMENT),
|
|
2972
|
+
get_payment: {
|
|
2973
|
+
payment: PAYMENT,
|
|
2974
|
+
transactions: section([{ id: S, type: S, status: S, amount: S, currency: S, createdAt: S }])
|
|
2975
|
+
},
|
|
2976
|
+
list_abandoned_carts: {
|
|
2977
|
+
carts: section([{
|
|
2978
|
+
id: S,
|
|
2979
|
+
abandonedAt: S,
|
|
2980
|
+
currency: S,
|
|
2981
|
+
itemCount: S,
|
|
2982
|
+
recoveryStatus: S,
|
|
2983
|
+
total: S,
|
|
2984
|
+
customer: { id: S }
|
|
2985
|
+
}]),
|
|
2986
|
+
summary: section({ abandonedCount: S, abandonedValue: S, recoveryEmailsSent: S, sinceDays: S })
|
|
2987
|
+
},
|
|
2988
|
+
search_events: page2(EVENT),
|
|
2989
|
+
list_webhook_deliveries: page2(DELIVERY),
|
|
2990
|
+
get_webhook_delivery: DELIVERY,
|
|
2991
|
+
explain_checkout_failure: {
|
|
2992
|
+
note: T,
|
|
2993
|
+
hint: T,
|
|
2994
|
+
session: {
|
|
2995
|
+
id: S,
|
|
2996
|
+
status: S,
|
|
2997
|
+
cartId: S,
|
|
2998
|
+
customerId: S,
|
|
2999
|
+
expiresAt: S,
|
|
3000
|
+
completedAt: S,
|
|
3001
|
+
allowedMethods: [S],
|
|
3002
|
+
collect: { shippingAddress: S, billingAddress: S, vatNumber: S },
|
|
3003
|
+
shippingAddress: ADDRESS,
|
|
3004
|
+
billingAddress: ADDRESS,
|
|
3005
|
+
recurringTerms: {
|
|
3006
|
+
interval: S,
|
|
3007
|
+
intervalUnit: S,
|
|
3008
|
+
intervalCount: S,
|
|
3009
|
+
totalPayments: S,
|
|
3010
|
+
taxApplies: S,
|
|
3011
|
+
planChargedToday: S,
|
|
3012
|
+
planName: T,
|
|
3013
|
+
trialDays: S,
|
|
3014
|
+
currency: S,
|
|
3015
|
+
firstChargeAmountMinor: S
|
|
3016
|
+
}
|
|
3017
|
+
},
|
|
3018
|
+
cart: section({
|
|
3019
|
+
id: S,
|
|
3020
|
+
status: S,
|
|
3021
|
+
currency: S,
|
|
3022
|
+
subtotal: S,
|
|
3023
|
+
discountTotal: S,
|
|
3024
|
+
shippingTotal: S,
|
|
3025
|
+
taxTotal: S,
|
|
3026
|
+
total: S,
|
|
3027
|
+
itemCount: S,
|
|
3028
|
+
items: [LINE_ITEM],
|
|
3029
|
+
lineItems: [LINE_ITEM]
|
|
3030
|
+
}),
|
|
3031
|
+
events: [EVENT],
|
|
3032
|
+
eventsUnavailable: T,
|
|
3033
|
+
recentWebhookDeliveries: section([DELIVERY])
|
|
3034
|
+
},
|
|
3035
|
+
list_extension_installations: page2({
|
|
3036
|
+
id: S,
|
|
3037
|
+
extensionId: S,
|
|
3038
|
+
extensionName: T,
|
|
3039
|
+
extensionSlug: S,
|
|
3040
|
+
versionId: S,
|
|
3041
|
+
status: S,
|
|
3042
|
+
environmentSlug: S,
|
|
3043
|
+
installedAt: S,
|
|
3044
|
+
uninstalledAt: S
|
|
3045
|
+
}),
|
|
3046
|
+
get_connector_routing: {
|
|
3047
|
+
paymentRouting: section({
|
|
3048
|
+
defaultCascade: [S],
|
|
3049
|
+
rules: [{ cascade: [S], conditions: [{ type: S, op: S, currency: S, min: S, max: S, values: [S] }] }]
|
|
3050
|
+
}),
|
|
3051
|
+
cardDecline: section({ rules: [{ errorCode: S, conditions: [{ type: S, op: S, values: [S] }] }] }),
|
|
3052
|
+
otherDecline: section({ rules: [{ errorCode: S, conditions: [{ type: S, op: S, values: [S] }] }] }),
|
|
3053
|
+
routingConnectors: section([{ id: S, displayName: T, method: S }])
|
|
3054
|
+
},
|
|
3055
|
+
get_integration_status: {
|
|
3056
|
+
environment: { slug: S, kind: S, providerEnvironment: S },
|
|
3057
|
+
workspace: { name: T, slug: S },
|
|
3058
|
+
applicationId: S,
|
|
3059
|
+
application: APPLICATION,
|
|
3060
|
+
connectors: section([{
|
|
3061
|
+
id: S,
|
|
3062
|
+
displayName: T,
|
|
3063
|
+
method: S,
|
|
3064
|
+
type: S,
|
|
3065
|
+
status: S,
|
|
3066
|
+
active: S,
|
|
3067
|
+
position: S,
|
|
3068
|
+
acceptedCountries: [S],
|
|
3069
|
+
acceptedCurrencies: [S],
|
|
3070
|
+
threeDSecureEnabled: S,
|
|
3071
|
+
createdAt: S
|
|
3072
|
+
}]),
|
|
3073
|
+
webhookEndpoints: section([{ id: S, isActive: S, enabledEvents: [S], createdAt: S }]),
|
|
3074
|
+
unsubscribedEvents: section({ windowDays: S, unsubscribed: [{ type: S, count: S, lastEmittedAt: S }] })
|
|
3075
|
+
},
|
|
3076
|
+
get_workspace_settings: { name: T, slug: S, brandColor: S, logoUrl: T, onboardingCompletedAt: S },
|
|
3077
|
+
// What happened and to which record; not which team member did it.
|
|
3078
|
+
get_audit_log: page2({ id: S, action: S, resourceType: S, resourceId: S, createdAt: S }),
|
|
3079
|
+
list_applications: [{ id: S, name: T, slug: S, currency: S, isActive: S }],
|
|
3080
|
+
// Analytics: computed aggregates, listed field by field all the same.
|
|
3081
|
+
get_business_summary: {
|
|
3082
|
+
period: S,
|
|
3083
|
+
range: RANGE,
|
|
3084
|
+
orders: section({ current: ORDER_PERIOD, previous: ORDER_PERIOD, deltaPct: S }),
|
|
3085
|
+
subscriptions: section({
|
|
3086
|
+
mrr: { currentCents: S, periodStartCents: S, deltaPct: S },
|
|
3087
|
+
newSubscriptions: S,
|
|
3088
|
+
cancelledSubscriptions: S,
|
|
3089
|
+
churnRate: S,
|
|
3090
|
+
note: T
|
|
3091
|
+
}),
|
|
3092
|
+
receivables: section({ outstandingCents: S, outstandingCount: S }),
|
|
3093
|
+
quotes: section({ sent: MONEY_COUNT, accepted: MONEY_COUNT, declined: MONEY_COUNT, expired: MONEY_COUNT })
|
|
3094
|
+
},
|
|
3095
|
+
get_attention_digest: {
|
|
3096
|
+
disputes: section({ open: S, totalCents: S }),
|
|
3097
|
+
subscriptionsInDunning: section(S),
|
|
3098
|
+
overdueInvoices: section(MONEY_COUNT),
|
|
3099
|
+
unhealthyWebhooks: section([{ endpointId: S, failureRate: S, attempts7d: S }])
|
|
3100
|
+
},
|
|
3101
|
+
get_sales_timeseries: {
|
|
3102
|
+
application: APPLICATION,
|
|
3103
|
+
range: RANGE,
|
|
3104
|
+
currency: S,
|
|
3105
|
+
days: S,
|
|
3106
|
+
note: T,
|
|
3107
|
+
series: [{ date: S, grossCents: S, orders: S }],
|
|
3108
|
+
totalGrossCents: S,
|
|
3109
|
+
totalOrders: S
|
|
3110
|
+
},
|
|
3111
|
+
// get_top_products has no entry, so it is not offered over OAuth: it ranks
|
|
3112
|
+
// products by line-item reference id or name, which a buyer can type.
|
|
3113
|
+
get_customer_growth: {
|
|
3114
|
+
application: APPLICATION,
|
|
3115
|
+
range: RANGE,
|
|
3116
|
+
note: T,
|
|
3117
|
+
topBy: S,
|
|
3118
|
+
signups: { total: S, series: [{ date: S, signups: S }] },
|
|
3119
|
+
ordersByCustomerType: { newCustomerOrders: S, returningCustomerOrders: S },
|
|
3120
|
+
// The ranking, by id: a top customer's name and email stay behind.
|
|
3121
|
+
topCustomers: [{ customerId: S, orderCount: S, revenueCents: S }],
|
|
3122
|
+
totals: { customerCount: S, orderCount: S, revenueCents: S }
|
|
3123
|
+
},
|
|
3124
|
+
get_revenue_breakdown: {
|
|
3125
|
+
application: APPLICATION,
|
|
3126
|
+
range: RANGE,
|
|
3127
|
+
dimension: S,
|
|
3128
|
+
note: T,
|
|
3129
|
+
rows: [{ key: S, orderCount: S, grossCents: S }],
|
|
3130
|
+
totals: { orderCount: S, grossCents: S }
|
|
3131
|
+
},
|
|
3132
|
+
get_funnel_conversion: {
|
|
3133
|
+
application: APPLICATION,
|
|
3134
|
+
range: RANGE,
|
|
3135
|
+
note: T,
|
|
3136
|
+
conversionRate: S,
|
|
3137
|
+
stages: [{ name: S, count: S }],
|
|
3138
|
+
dropOff: [{ from: S, to: S, ratePct: S }]
|
|
3139
|
+
},
|
|
3140
|
+
get_subscription_movement: {
|
|
3141
|
+
application: APPLICATION,
|
|
3142
|
+
range: RANGE,
|
|
3143
|
+
note: T,
|
|
3144
|
+
series: [{ date: S, newSubscriptions: S, churnedSubscriptions: S, mrrDeltaCents: S }],
|
|
3145
|
+
totals: { newSubscriptions: S, churnedSubscriptions: S, netMrrDeltaCents: S }
|
|
3146
|
+
},
|
|
3147
|
+
get_discount_performance: {
|
|
3148
|
+
application: APPLICATION,
|
|
3149
|
+
range: RANGE,
|
|
3150
|
+
note: T,
|
|
3151
|
+
rows: [{ discountId: S, code: T, redemptions: S, discountedRevenueCents: S, discountGivenCents: S }],
|
|
3152
|
+
totals: { redemptions: S, discountedRevenueCents: S, discountGivenCents: S }
|
|
3153
|
+
}
|
|
3154
|
+
};
|
|
3155
|
+
|
|
1584
3156
|
// src/server.ts
|
|
1585
|
-
var VERSION =
|
|
3157
|
+
var VERSION = true ? "0.2.2" : "0.0.0-dev";
|
|
1586
3158
|
function registerableActions(policy) {
|
|
1587
3159
|
const isProduction = policy.identity.environment.providerEnvironment === "production";
|
|
1588
3160
|
return actionsForChannel("mcp").filter((action) => {
|
|
1589
3161
|
if (!HANDLERS[action.id]) return false;
|
|
3162
|
+
if (action.credentialScoped && policy.allowCredentialScoped === false) return false;
|
|
1590
3163
|
if (!canRunAction(policy.identity.scopes, action)) return false;
|
|
1591
3164
|
if (action.kind === "read") return true;
|
|
1592
|
-
if (action.sandboxOnly) return !isProduction;
|
|
3165
|
+
if (action.sandboxOnly) return !isProduction && (policy.allowSandboxWrites ?? true);
|
|
1593
3166
|
if (!policy.allowWrites) return false;
|
|
1594
3167
|
return !isProduction || policy.allowLiveWrites;
|
|
1595
3168
|
});
|
|
@@ -1606,11 +3179,14 @@ function buildInstructions(identity, actions, policy) {
|
|
|
1606
3179
|
`Throttle MCP ${VERSION}. Connected to ${where}, environment "${identity.environment.slug}" (${isProduction ? "PRODUCTION \u2014 live data" : "non-production \u2014 not live data"}).`,
|
|
1607
3180
|
"",
|
|
1608
3181
|
"Conventions:",
|
|
1609
|
-
"-
|
|
3182
|
+
"- The workspace and environment are fixed for this session and cannot be switched from inside a tool call. Call whoami if you need to state where you are.",
|
|
3183
|
+
'- The application can vary per call: pass "applicationId" on a read or analytics tool to reach a different application in this workspace, and call list_applications to see which ones this connection can reach. Omitting applicationId uses the application this connection was authorized for.',
|
|
3184
|
+
"- Every analytics result covers exactly ONE application and names it in `application`. Say which application a figure describes. If the user asks about their business as a whole and this connection reaches more than one application, do not answer from the default alone \u2014 call list_applications, then query each and add them up, or ask which application they mean.",
|
|
1610
3185
|
'- List tools return { data, pagination }. When pagination.hasMore is true, pass pagination.cursor back as "cursor" rather than raising "limit".',
|
|
1611
3186
|
"- Amounts are integer minor units (2500 = $25.00).",
|
|
1612
3187
|
"- Throttle rejects snake_case on public request bodies. Before writing integration code, confirm shapes with describe_endpoint and check bodies with validate_payload instead of guessing field names.",
|
|
1613
|
-
"- API errors come back as text with a stable code. On insufficient_scopes, tell the user which scope to add \u2014 do not retry."
|
|
3188
|
+
"- API errors come back as text with a stable code. On insufficient_scopes, tell the user which scope to add \u2014 do not retry.",
|
|
3189
|
+
"- Analytics results may mark a section `omitted`. That means UNKNOWN, never zero. When `grantable` is false the data cannot be read over this connection at all \u2014 say so rather than reporting an absence."
|
|
1614
3190
|
];
|
|
1615
3191
|
if (liveWrites.length === 0) {
|
|
1616
3192
|
lines.push(
|
|
@@ -1628,8 +3204,31 @@ function buildInstructions(identity, actions, policy) {
|
|
|
1628
3204
|
}
|
|
1629
3205
|
return lines.join("\n");
|
|
1630
3206
|
}
|
|
3207
|
+
function declareSecuritySchemes(server, actions) {
|
|
3208
|
+
const inner = server.server;
|
|
3209
|
+
const handlers = inner._requestHandlers;
|
|
3210
|
+
const original = handlers?.get("tools/list");
|
|
3211
|
+
if (!original) return;
|
|
3212
|
+
const schemes = new Map(
|
|
3213
|
+
actions.map((a) => [
|
|
3214
|
+
a.id,
|
|
3215
|
+
[{
|
|
3216
|
+
type: "oauth2",
|
|
3217
|
+
scopes: [.../* @__PURE__ */ new Set([...a.scopes, ...a.sectionScopes ?? []])].filter((scope) => validateScopesForOAuth([scope]).ok)
|
|
3218
|
+
}]
|
|
3219
|
+
])
|
|
3220
|
+
);
|
|
3221
|
+
inner.setRequestHandler(import_types.ListToolsRequestSchema, async (request, extra) => {
|
|
3222
|
+
const result = await original(request, extra);
|
|
3223
|
+
return {
|
|
3224
|
+
...result,
|
|
3225
|
+
tools: result.tools.map((tool) => ({ ...tool, securitySchemes: schemes.get(tool.name) ?? [{ type: "oauth2", scopes: [] }] }))
|
|
3226
|
+
};
|
|
3227
|
+
});
|
|
3228
|
+
}
|
|
1631
3229
|
function createServer(options) {
|
|
1632
|
-
const
|
|
3230
|
+
const redactPii = options.identity.type === "oauth_token" && !hasScope(options.identity.scopes, "customer_pii:read");
|
|
3231
|
+
const actions = registerableActions(options).filter((a) => !redactPii || OAUTH_PROJECTIONS[a.id] !== void 0);
|
|
1633
3232
|
const server = new import_mcp.McpServer(
|
|
1634
3233
|
{ name: "throttle", version: VERSION },
|
|
1635
3234
|
{ instructions: buildInstructions(options.identity, actions, options) }
|
|
@@ -1642,25 +3241,57 @@ function createServer(options) {
|
|
|
1642
3241
|
};
|
|
1643
3242
|
for (const action of actions) {
|
|
1644
3243
|
const handler = HANDLERS[action.id];
|
|
1645
|
-
server.
|
|
3244
|
+
server.registerTool(
|
|
1646
3245
|
action.id,
|
|
1647
|
-
|
|
1648
|
-
|
|
3246
|
+
{
|
|
3247
|
+
title: action.title,
|
|
3248
|
+
description: describeForModel(action),
|
|
3249
|
+
inputSchema: fieldsToZodShape(action.input),
|
|
3250
|
+
// Every hint is an explicit boolean: directory reviews (OpenAI's in
|
|
3251
|
+
// particular) read a missing value as "unknown", not as false.
|
|
3252
|
+
annotations: {
|
|
3253
|
+
title: action.title,
|
|
3254
|
+
readOnlyHint: action.kind === "read",
|
|
3255
|
+
destructiveHint: action.kind === "write" && Boolean(action.destructive),
|
|
3256
|
+
idempotentHint: Boolean(action.idempotent),
|
|
3257
|
+
// Every tool is bounded to the connected workspace; none reaches the
|
|
3258
|
+
// public internet or an open-ended set of external entities.
|
|
3259
|
+
openWorldHint: false
|
|
3260
|
+
}
|
|
3261
|
+
},
|
|
1649
3262
|
async (args) => {
|
|
1650
3263
|
try {
|
|
3264
|
+
if (redactPii && action.id === "search_customers" && typeof args?.q === "string" && args.q.trim() !== "") {
|
|
3265
|
+
return {
|
|
3266
|
+
content: [{ type: "text", text: "Customer text search is not available over this connection, because names, emails, phone numbers and external ids are withheld. List customers by creation date (createdAfter/createdBefore), or open one by its Throttle id from an order, quote or subscription." }],
|
|
3267
|
+
isError: true
|
|
3268
|
+
};
|
|
3269
|
+
}
|
|
1651
3270
|
const result = await handler(ctx, args ?? {});
|
|
1652
|
-
|
|
3271
|
+
const shown = redactPii ? redactShopperPii(project(result, OAUTH_PROJECTIONS[action.id])) : result;
|
|
3272
|
+
return { content: [{ type: "text", text: formatResult(shown) }] };
|
|
1653
3273
|
} catch (error) {
|
|
1654
3274
|
return {
|
|
1655
3275
|
content: [{ type: "text", text: formatError(error) }],
|
|
1656
|
-
isError: true
|
|
3276
|
+
isError: true,
|
|
3277
|
+
...options.authChallenge && isCredentialRejection(error) ? {
|
|
3278
|
+
_meta: {
|
|
3279
|
+
"mcp/www_authenticate": [
|
|
3280
|
+
`${options.authChallenge}, error="invalid_token", error_description="The connected credential was rejected. Sign in again to continue."`
|
|
3281
|
+
]
|
|
3282
|
+
}
|
|
3283
|
+
} : {}
|
|
1657
3284
|
};
|
|
1658
3285
|
}
|
|
1659
3286
|
}
|
|
1660
3287
|
);
|
|
1661
3288
|
}
|
|
3289
|
+
if (options.authChallenge) declareSecuritySchemes(server, actions);
|
|
1662
3290
|
return server;
|
|
1663
3291
|
}
|
|
3292
|
+
function isCredentialRejection(error) {
|
|
3293
|
+
return error?.statusCode === 401;
|
|
3294
|
+
}
|
|
1664
3295
|
function describeForModel(action) {
|
|
1665
3296
|
const parts = [action.description];
|
|
1666
3297
|
if (action.guardrails && action.guardrails.length > 0) {
|