@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.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// src/server.ts
|
|
2
2
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
3
4
|
|
|
4
5
|
// ../core-scopes/src/scopes.ts
|
|
5
6
|
function hasScope(granted, required) {
|
|
@@ -28,6 +29,8 @@ function add(group, resource, actions, labels, opts = {}) {
|
|
|
28
29
|
staffOnly: opts.staffOnly ?? false,
|
|
29
30
|
// publishable defaults false; only the curated set below is true
|
|
30
31
|
publishableAllowed: opts.publishableAllowed ?? false,
|
|
32
|
+
// publishableOnly defaults false; only customer_accounts:write sets it
|
|
33
|
+
publishableOnly: opts.publishableOnly ?? false,
|
|
31
34
|
// extension default true for commerce scopes; Taxonomy B passes false
|
|
32
35
|
extensionAllowed: opts.extensionAllowed ?? true,
|
|
33
36
|
implies
|
|
@@ -38,19 +41,36 @@ add("Orders", "orders", ["read", "write"], { read: "View orders", write: "Manage
|
|
|
38
41
|
add("Quotes", "quotes", ["read", "write"], { read: "View quotes", write: "Manage quotes" });
|
|
39
42
|
add("Orders", "order_cancellations", ["write"], { write: "Cancel orders" });
|
|
40
43
|
add("Orders", "order_returns", ["read", "write"], { read: "View returns", write: "Process returns" });
|
|
44
|
+
add("Orders", "order_comps", ["write"], { write: "Comp orders (write off the balance)" }, { sensitive: true });
|
|
45
|
+
add("Orders", "order_holds", ["write"], { write: "Freeze and unfreeze orders" });
|
|
46
|
+
add(
|
|
47
|
+
"Imports",
|
|
48
|
+
"imports",
|
|
49
|
+
["read", "write"],
|
|
50
|
+
{ read: "View data imports", write: "Run and undo data imports" },
|
|
51
|
+
{ extensionAllowed: false }
|
|
52
|
+
);
|
|
41
53
|
add("Payments", "payments", ["read", "write"], { read: "View payments", write: "Create payments" });
|
|
42
54
|
add("Payments", "payment_authorizations", ["write"], { write: "Authorize payments" });
|
|
43
55
|
add("Payments", "payment_captures", ["write"], { write: "Capture payments" });
|
|
44
56
|
add("Payments", "payment_refunds", ["read", "write"], { read: "View refunds", write: "Refund payments" }, { sensitive: true });
|
|
45
57
|
add("Payments", "payment_voids", ["write"], { write: "Void payments" }, { sensitive: true });
|
|
58
|
+
add("Payments", "payment_records", ["write"], { write: "Record payments received outside Throttle" }, { sensitive: true });
|
|
46
59
|
add("Payments", "payment_disputes", ["read", "write"], { read: "View disputes", write: "Handle disputes" }, { sensitive: true });
|
|
47
60
|
add("Payments", "payment_methods", ["read", "write"], { read: "View payment methods", write: "Tokenize payment methods" }, { sensitive: true });
|
|
48
61
|
add("Customers", "customers", ["read", "write"], { read: "View customers", write: "Manage customers" });
|
|
49
62
|
add("Customers", "customer_pii", ["read"], { read: "View customer PII" }, { sensitive: true });
|
|
50
63
|
add("Customers", "customer_addresses", ["read", "write"], { read: "View addresses", write: "Manage addresses" });
|
|
51
64
|
add("Customers", "customer_payment_methods", ["read", "write"], { read: "View stored cards", write: "Manage stored cards" }, { sensitive: true });
|
|
65
|
+
add(
|
|
66
|
+
"Customer accounts",
|
|
67
|
+
"customer_accounts",
|
|
68
|
+
["write"],
|
|
69
|
+
{ write: "Let shoppers register, sign in and manage their account" },
|
|
70
|
+
{ publishableAllowed: true, publishableOnly: true, extensionAllowed: false }
|
|
71
|
+
);
|
|
52
72
|
add("Carts", "carts", ["read", "write"], { read: "View carts", write: "Manage carts" });
|
|
53
|
-
add("Carts", "cart_items", ["
|
|
73
|
+
add("Carts", "cart_items", ["write"], { write: "Manage line items" });
|
|
54
74
|
add("Carts", "checkout_sessions", ["read", "write"], { read: "View checkout sessions", write: "Manage checkout sessions" });
|
|
55
75
|
add("Discounts", "discounts", ["read", "write"], { read: "View discounts", write: "Manage discounts" });
|
|
56
76
|
add("Subscriptions", "subscriptions", ["read", "write"], { read: "View subscriptions", write: "Manage subscriptions" });
|
|
@@ -71,8 +91,32 @@ add("Shipping & Tax", "shipping_quotes", ["write"], { write: "Request shipping q
|
|
|
71
91
|
add("Shipping & Tax", "tax_calculations", ["write"], { write: "Calculate tax" }, { publishableAllowed: true });
|
|
72
92
|
add("Shipping & Tax", "shipping_tax_quote_tokens", ["read", "write"], { read: "View quote tokens", write: "Manage quote tokens" });
|
|
73
93
|
add("Shipping & Tax", "shipping_tax_snapshots", ["write"], { write: "Accept external snapshots" });
|
|
94
|
+
add(
|
|
95
|
+
"Scripts",
|
|
96
|
+
"storefront_scripts",
|
|
97
|
+
["read", "write"],
|
|
98
|
+
{
|
|
99
|
+
read: "Load storefront scripts",
|
|
100
|
+
// `write` covers exactly one thing: reporting a script's own load outcome
|
|
101
|
+
// from the merchant's storefront, so the health panel stops being
|
|
102
|
+
// hosted-checkout-only. On checkout the session id in the URL is the
|
|
103
|
+
// capability that scopes that write; a storefront page has no session, so
|
|
104
|
+
// the pk it already holds is what scopes it instead. It writes nothing but
|
|
105
|
+
// load telemetry — authoring stays on `application_scripts:write`, which
|
|
106
|
+
// is not publishable-allowed.
|
|
107
|
+
write: "Report storefront script load outcomes"
|
|
108
|
+
},
|
|
109
|
+
{ publishableAllowed: true }
|
|
110
|
+
);
|
|
111
|
+
add("Scripts", "application_scripts", ["read", "write"], {
|
|
112
|
+
read: "View merchant-authored scripts",
|
|
113
|
+
write: "Create, edit and disable merchant-authored scripts"
|
|
114
|
+
}, { extensionAllowed: false });
|
|
115
|
+
add("Scripts", "extension_scripts", ["read", "write"], {
|
|
116
|
+
read: "View an extension's buyer-facing scripts",
|
|
117
|
+
write: "Create, edit and delete an extension's buyer-facing scripts"
|
|
118
|
+
}, { extensionAllowed: false });
|
|
74
119
|
add("Connectors", "connectors", ["read", "write"], { read: "View connectors", write: "Manage connectors" });
|
|
75
|
-
add("Connectors", "connector_activations", ["write"], { write: "Activate/deactivate connectors" });
|
|
76
120
|
add("Webhooks & Events", "webhooks", ["read", "write"], { read: "View webhooks", write: "Manage webhooks" });
|
|
77
121
|
add("Webhooks & Events", "webhook_deliveries", ["read", "write"], { read: "View deliveries", write: "Retry deliveries" });
|
|
78
122
|
add("Webhooks & Events", "events", ["read"], { read: "View events" });
|
|
@@ -88,7 +132,7 @@ add("Platform billing", "billing", ["read", "write"], { read: "View billing", wr
|
|
|
88
132
|
add("Platform billing", "billing_payment_methods", ["read", "write"], { read: "View billing cards", write: "Manage billing cards" }, HUMAN);
|
|
89
133
|
add("Email", "email_templates", ["read", "write"], { read: "View email templates", write: "Edit email templates" }, HUMAN);
|
|
90
134
|
add("Email", "email_settings", ["read", "write"], { read: "View email settings", write: "Edit email settings" }, HUMAN);
|
|
91
|
-
add("Go-live", "go_live", ["
|
|
135
|
+
add("Go-live", "go_live", ["write"], { write: "Submit go-live" }, HUMAN);
|
|
92
136
|
add("Partner", "partner", ["read", "write"], { read: "View partner profile", write: "Edit partner profile" }, HUMAN);
|
|
93
137
|
add("Partner", "affiliates", ["read", "write"], { read: "View affiliates", write: "Manage affiliates" }, HUMAN);
|
|
94
138
|
add("Notifications", "notifications", ["write"], { write: "Send notifications" }, HUMAN);
|
|
@@ -112,6 +156,7 @@ REG["extensions:install"] = {
|
|
|
112
156
|
sensitive: false,
|
|
113
157
|
staffOnly: false,
|
|
114
158
|
publishableAllowed: false,
|
|
159
|
+
publishableOnly: false,
|
|
115
160
|
extensionAllowed: false,
|
|
116
161
|
implies: []
|
|
117
162
|
};
|
|
@@ -124,6 +169,7 @@ REG["*"] = {
|
|
|
124
169
|
sensitive: true,
|
|
125
170
|
staffOnly: false,
|
|
126
171
|
publishableAllowed: false,
|
|
172
|
+
publishableOnly: false,
|
|
127
173
|
extensionAllowed: false,
|
|
128
174
|
implies: []
|
|
129
175
|
};
|
|
@@ -143,6 +189,9 @@ var EVENT_SCOPE_OVERRIDES = {
|
|
|
143
189
|
"payment.refunded": "payment_refunds:read",
|
|
144
190
|
"payment.partially_refunded": "payment_refunds:read",
|
|
145
191
|
"payment.refund_failed": "payment_refunds:read",
|
|
192
|
+
// Money returned, so it belongs with the other refund events rather than
|
|
193
|
+
// behind the broad subscriptions:read the `subscription.` prefix would give.
|
|
194
|
+
"subscription.invoice_refunded": "payment_refunds:read",
|
|
146
195
|
"payment.disputed": "payment_disputes:read",
|
|
147
196
|
"payment.dispute_cleared": "payment_disputes:read",
|
|
148
197
|
"fulfillment.digital.delivered": "fulfillment_digital:read"
|
|
@@ -159,9 +208,17 @@ var PREFIX_RULES = [
|
|
|
159
208
|
["fulfillment.", "fulfillments:read"],
|
|
160
209
|
["discount.", "discounts:read"],
|
|
161
210
|
["invoice.", "invoices:read"],
|
|
211
|
+
["import.", "imports:read"],
|
|
162
212
|
["shipping_tax.", "shipping_tax:read"],
|
|
163
213
|
["workspace.", "billing:read"],
|
|
164
|
-
|
|
214
|
+
// `extensions:read` is a human scope (extensions must never watch other
|
|
215
|
+
// extensions), so only a workspace endpoint can SUBSCRIBE to this family.
|
|
216
|
+
// The uninstalled installation's own endpoint is addressed directly by the
|
|
217
|
+
// uninstall path and needs no subscription at all.
|
|
218
|
+
["extension.", "extensions:read"],
|
|
219
|
+
// Subscribing to script outcomes needs script read — the payload names the
|
|
220
|
+
// script key and its hash, which is the merchant's script inventory.
|
|
221
|
+
["script.", "application_scripts:read"]
|
|
165
222
|
];
|
|
166
223
|
function eventReadScope(type) {
|
|
167
224
|
if (EVENT_SCOPE_OVERRIDES[type]) return EVENT_SCOPE_OVERRIDES[type];
|
|
@@ -173,8 +230,90 @@ function eventReadScope(type) {
|
|
|
173
230
|
|
|
174
231
|
// ../core-scopes/src/issuance.ts
|
|
175
232
|
var PUBLISHABLE_SCOPES = Object.values(SCOPE_REGISTRY).filter((d) => d.publishableAllowed).map((d) => d.id);
|
|
233
|
+
var CONNECTOR_GRANTABLE_SENSITIVE_SCOPES = ["customer_pii:read"];
|
|
234
|
+
function validateScopesForOAuth(scopes, opts = {}) {
|
|
235
|
+
const invalid = [];
|
|
236
|
+
for (const id of scopes) {
|
|
237
|
+
if (id === "*") {
|
|
238
|
+
invalid.push(id);
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
241
|
+
const def = SCOPE_REGISTRY[id];
|
|
242
|
+
if (!def || def.staffOnly) {
|
|
243
|
+
invalid.push(id);
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
if (def.sensitive && !(opts.connector && CONNECTOR_GRANTABLE_SENSITIVE_SCOPES.includes(id))) invalid.push(id);
|
|
247
|
+
}
|
|
248
|
+
return { ok: invalid.length === 0, invalid };
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// ../core-scopes/src/shopper-pii.ts
|
|
252
|
+
var PERSON_KEYS = /* @__PURE__ */ new Set([
|
|
253
|
+
"email",
|
|
254
|
+
"customerEmail",
|
|
255
|
+
"billingEmail",
|
|
256
|
+
"buyerEmail",
|
|
257
|
+
"authorEmail",
|
|
258
|
+
"authorName",
|
|
259
|
+
"phone",
|
|
260
|
+
"firstName",
|
|
261
|
+
"lastName",
|
|
262
|
+
"addressLine1",
|
|
263
|
+
"addressLine2",
|
|
264
|
+
"billingAddressLines",
|
|
265
|
+
"processorResponse",
|
|
266
|
+
"shareToken",
|
|
267
|
+
"company",
|
|
268
|
+
"externalId",
|
|
269
|
+
"externalCustomerId",
|
|
270
|
+
// Free-form: contents cannot be inspected for a person, so the field goes.
|
|
271
|
+
"metadata",
|
|
272
|
+
"notes",
|
|
273
|
+
"internalNotes",
|
|
274
|
+
"buyerNote",
|
|
275
|
+
"buyerReference",
|
|
276
|
+
"changeSummary",
|
|
277
|
+
"declinedReason",
|
|
278
|
+
"compReason",
|
|
279
|
+
"holdReason",
|
|
280
|
+
"body",
|
|
281
|
+
"acceptance",
|
|
282
|
+
"acceptanceSelections",
|
|
283
|
+
"clientContext",
|
|
284
|
+
"ipAddress",
|
|
285
|
+
"userAgent",
|
|
286
|
+
"vatNumber",
|
|
287
|
+
"buyerVatNumber",
|
|
288
|
+
"taxExemptionNumber"
|
|
289
|
+
]);
|
|
290
|
+
var PERSON_CONTAINERS = /* @__PURE__ */ new Set(["customer", "buyer"]);
|
|
291
|
+
function isPlainObject(value) {
|
|
292
|
+
if (value === null || typeof value !== "object") return false;
|
|
293
|
+
const proto = Object.getPrototypeOf(value);
|
|
294
|
+
return proto === Object.prototype || proto === null;
|
|
295
|
+
}
|
|
296
|
+
function walk(value, parentKey, depth) {
|
|
297
|
+
if (depth > 32) return value;
|
|
298
|
+
if (Array.isArray(value)) return value.map((item) => walk(item, parentKey, depth + 1));
|
|
299
|
+
if (!isPlainObject(value)) return value;
|
|
300
|
+
const out = {};
|
|
301
|
+
const isPerson = parentKey !== null && PERSON_CONTAINERS.has(parentKey);
|
|
302
|
+
for (const [key, child] of Object.entries(value)) {
|
|
303
|
+
if (PERSON_KEYS.has(key) || isPerson && key === "name") {
|
|
304
|
+
out[key] = child === void 0 ? child : null;
|
|
305
|
+
} else {
|
|
306
|
+
out[key] = walk(child, key, depth + 1);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
return out;
|
|
310
|
+
}
|
|
311
|
+
function redactShopperPii(value) {
|
|
312
|
+
return walk(value, null, 0);
|
|
313
|
+
}
|
|
176
314
|
|
|
177
315
|
// ../action-catalog/src/actions.ts
|
|
316
|
+
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.";
|
|
178
317
|
var ACTIONS = [
|
|
179
318
|
// ---------------------------------------------------------------- identity
|
|
180
319
|
{
|
|
@@ -191,7 +330,7 @@ var ACTIONS = [
|
|
|
191
330
|
{
|
|
192
331
|
id: "search_docs",
|
|
193
332
|
title: "Search the Throttle developer docs",
|
|
194
|
-
description: "Full-text search over the Throttle developer documentation. Returns titles, canonical
|
|
333
|
+
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.",
|
|
195
334
|
kind: "read",
|
|
196
335
|
scopes: [],
|
|
197
336
|
input: [
|
|
@@ -228,7 +367,7 @@ var ACTIONS = [
|
|
|
228
367
|
{
|
|
229
368
|
id: "validate_payload",
|
|
230
369
|
title: "Validate a request body against the API contract",
|
|
231
|
-
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.",
|
|
370
|
+
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.",
|
|
232
371
|
kind: "read",
|
|
233
372
|
scopes: [],
|
|
234
373
|
input: [
|
|
@@ -243,15 +382,22 @@ var ACTIONS = [
|
|
|
243
382
|
{
|
|
244
383
|
id: "api_get",
|
|
245
384
|
title: "Read any Throttle API endpoint",
|
|
246
|
-
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.",
|
|
385
|
+
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.",
|
|
247
386
|
kind: "read",
|
|
248
387
|
scopes: [],
|
|
388
|
+
credentialScoped: true,
|
|
249
389
|
input: [
|
|
250
390
|
{
|
|
251
391
|
name: "path",
|
|
252
392
|
type: "string",
|
|
253
393
|
required: true,
|
|
254
394
|
description: "Path beginning with /api/v1, query string included."
|
|
395
|
+
},
|
|
396
|
+
{
|
|
397
|
+
name: "applicationId",
|
|
398
|
+
type: "string",
|
|
399
|
+
required: false,
|
|
400
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
255
401
|
}
|
|
256
402
|
],
|
|
257
403
|
requests: [],
|
|
@@ -273,11 +419,33 @@ var ACTIONS = [
|
|
|
273
419
|
{ name: "q", type: "string", required: false, description: "Free-text search." },
|
|
274
420
|
{ name: "status", type: "string", required: false, description: "Order status filter." },
|
|
275
421
|
{ name: "paymentStatus", type: "string", required: false, description: "Payment status filter." },
|
|
422
|
+
{
|
|
423
|
+
name: "createdAfter",
|
|
424
|
+
type: "string",
|
|
425
|
+
required: false,
|
|
426
|
+
description: "ISO 8601 lower bound on creation time, exclusive. For a single day, pass that day's midnight and the next day's midnight."
|
|
427
|
+
},
|
|
428
|
+
{
|
|
429
|
+
name: "createdBefore",
|
|
430
|
+
type: "string",
|
|
431
|
+
required: false,
|
|
432
|
+
description: "ISO 8601 upper bound on creation time, exclusive."
|
|
433
|
+
},
|
|
276
434
|
{ name: "limit", type: "number", required: false, description: "Page size." },
|
|
277
|
-
{ name: "cursor", type: "string", required: false, description: "Pagination cursor." }
|
|
435
|
+
{ name: "cursor", type: "string", required: false, description: "Pagination cursor." },
|
|
436
|
+
{
|
|
437
|
+
name: "applicationId",
|
|
438
|
+
type: "string",
|
|
439
|
+
required: false,
|
|
440
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
441
|
+
}
|
|
278
442
|
],
|
|
279
443
|
requests: [{ method: "GET", path: "/api/v1/orders" }],
|
|
280
|
-
channels: ["mcp", "zapier", "make", "n8n"]
|
|
444
|
+
channels: ["mcp", "zapier", "make", "n8n"],
|
|
445
|
+
guardrails: [
|
|
446
|
+
OAUTH_WITHHELD,
|
|
447
|
+
"Over an OAuth connection, q matches the order id, order number and status fields only."
|
|
448
|
+
]
|
|
281
449
|
},
|
|
282
450
|
{
|
|
283
451
|
id: "get_order",
|
|
@@ -285,7 +453,16 @@ var ACTIONS = [
|
|
|
285
453
|
description: "Full picture of a single order: the order itself plus its payments, returns and fulfillments in one response.",
|
|
286
454
|
kind: "read",
|
|
287
455
|
scopes: ["orders:read"],
|
|
288
|
-
|
|
456
|
+
sectionScopes: ["payments:read", "order_returns:read", "fulfillments:read"],
|
|
457
|
+
input: [
|
|
458
|
+
{ name: "id", type: "string", required: true, description: "Order id." },
|
|
459
|
+
{
|
|
460
|
+
name: "applicationId",
|
|
461
|
+
type: "string",
|
|
462
|
+
required: false,
|
|
463
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
464
|
+
}
|
|
465
|
+
],
|
|
289
466
|
requests: [
|
|
290
467
|
{ method: "GET", path: "/api/v1/orders/:id" },
|
|
291
468
|
{ method: "GET", path: "/api/v1/orders/:id/payments" },
|
|
@@ -294,23 +471,45 @@ var ACTIONS = [
|
|
|
294
471
|
],
|
|
295
472
|
channels: ["mcp"],
|
|
296
473
|
guardrails: [
|
|
297
|
-
"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."
|
|
474
|
+
"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.",
|
|
475
|
+
OAUTH_WITHHELD
|
|
298
476
|
]
|
|
299
477
|
},
|
|
300
478
|
// ---------------------------------------------------------------- customers
|
|
301
479
|
{
|
|
302
480
|
id: "search_customers",
|
|
303
481
|
title: "Search customers",
|
|
304
|
-
description: "Find customers by name, email or external id.",
|
|
482
|
+
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.",
|
|
305
483
|
kind: "read",
|
|
306
484
|
scopes: ["customers:read"],
|
|
307
485
|
input: [
|
|
308
486
|
{ name: "q", type: "string", required: false, description: "Free-text search." },
|
|
487
|
+
{
|
|
488
|
+
name: "createdAfter",
|
|
489
|
+
type: "string",
|
|
490
|
+
required: false,
|
|
491
|
+
description: "ISO 8601 lower bound on creation time, exclusive. For a single day, pass that day's midnight and the next day's midnight."
|
|
492
|
+
},
|
|
493
|
+
{
|
|
494
|
+
name: "createdBefore",
|
|
495
|
+
type: "string",
|
|
496
|
+
required: false,
|
|
497
|
+
description: "ISO 8601 upper bound on creation time, exclusive."
|
|
498
|
+
},
|
|
309
499
|
{ name: "limit", type: "number", required: false, description: "Page size." },
|
|
310
|
-
{ name: "cursor", type: "string", required: false, description: "Pagination cursor." }
|
|
500
|
+
{ name: "cursor", type: "string", required: false, description: "Pagination cursor." },
|
|
501
|
+
{
|
|
502
|
+
name: "applicationId",
|
|
503
|
+
type: "string",
|
|
504
|
+
required: false,
|
|
505
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
506
|
+
}
|
|
311
507
|
],
|
|
312
508
|
requests: [{ method: "GET", path: "/api/v1/customers" }],
|
|
313
|
-
channels: ["mcp", "zapier", "make", "n8n"]
|
|
509
|
+
channels: ["mcp", "zapier", "make", "n8n"],
|
|
510
|
+
guardrails: [
|
|
511
|
+
'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.'
|
|
512
|
+
]
|
|
314
513
|
},
|
|
315
514
|
{
|
|
316
515
|
id: "get_customer",
|
|
@@ -318,7 +517,16 @@ var ACTIONS = [
|
|
|
318
517
|
description: "Full picture of a single customer: the record plus their subscriptions and addresses in one response.",
|
|
319
518
|
kind: "read",
|
|
320
519
|
scopes: ["customers:read"],
|
|
321
|
-
|
|
520
|
+
sectionScopes: ["customer_addresses:read", "subscriptions:read"],
|
|
521
|
+
input: [
|
|
522
|
+
{ name: "id", type: "string", required: true, description: "Customer id." },
|
|
523
|
+
{
|
|
524
|
+
name: "applicationId",
|
|
525
|
+
type: "string",
|
|
526
|
+
required: false,
|
|
527
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
528
|
+
}
|
|
529
|
+
],
|
|
322
530
|
requests: [
|
|
323
531
|
{ method: "GET", path: "/api/v1/customers/:id" },
|
|
324
532
|
{ method: "GET", path: "/api/v1/customers/:id/addresses" },
|
|
@@ -326,7 +534,9 @@ var ACTIONS = [
|
|
|
326
534
|
],
|
|
327
535
|
channels: ["mcp"],
|
|
328
536
|
guardrails: [
|
|
329
|
-
"Sub-reads need customer_addresses:read / subscriptions:read. Omit a section the credential cannot read instead of failing."
|
|
537
|
+
"Sub-reads need customer_addresses:read / subscriptions:read. Omit a section the credential cannot read instead of failing.",
|
|
538
|
+
"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.",
|
|
539
|
+
OAUTH_WITHHELD
|
|
330
540
|
]
|
|
331
541
|
},
|
|
332
542
|
// ------------------------------------------------------------- subscriptions
|
|
@@ -339,8 +549,26 @@ var ACTIONS = [
|
|
|
339
549
|
input: [
|
|
340
550
|
{ name: "customerId", type: "string", required: false, description: "Filter by customer." },
|
|
341
551
|
{ name: "status", type: "string", required: false, description: "Filter by status." },
|
|
552
|
+
{
|
|
553
|
+
name: "createdAfter",
|
|
554
|
+
type: "string",
|
|
555
|
+
required: false,
|
|
556
|
+
description: "ISO 8601 lower bound on creation time, exclusive. For a single day, pass that day's midnight and the next day's midnight."
|
|
557
|
+
},
|
|
558
|
+
{
|
|
559
|
+
name: "createdBefore",
|
|
560
|
+
type: "string",
|
|
561
|
+
required: false,
|
|
562
|
+
description: "ISO 8601 upper bound on creation time, exclusive."
|
|
563
|
+
},
|
|
342
564
|
{ name: "limit", type: "number", required: false, description: "Page size." },
|
|
343
|
-
{ name: "cursor", type: "string", required: false, description: "Pagination cursor." }
|
|
565
|
+
{ name: "cursor", type: "string", required: false, description: "Pagination cursor." },
|
|
566
|
+
{
|
|
567
|
+
name: "applicationId",
|
|
568
|
+
type: "string",
|
|
569
|
+
required: false,
|
|
570
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
571
|
+
}
|
|
344
572
|
],
|
|
345
573
|
requests: [{ method: "GET", path: "/api/v1/subscriptions" }],
|
|
346
574
|
channels: ["mcp", "zapier", "make", "n8n"]
|
|
@@ -351,7 +579,15 @@ var ACTIONS = [
|
|
|
351
579
|
description: "Read a single subscription including its current period and pending changes.",
|
|
352
580
|
kind: "read",
|
|
353
581
|
scopes: ["subscriptions:read"],
|
|
354
|
-
input: [
|
|
582
|
+
input: [
|
|
583
|
+
{ name: "id", type: "string", required: true, description: "Subscription id." },
|
|
584
|
+
{
|
|
585
|
+
name: "applicationId",
|
|
586
|
+
type: "string",
|
|
587
|
+
required: false,
|
|
588
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
589
|
+
}
|
|
590
|
+
],
|
|
355
591
|
requests: [{ method: "GET", path: "/api/v1/subscriptions/:id" }],
|
|
356
592
|
channels: ["mcp", "zapier", "make", "n8n"]
|
|
357
593
|
},
|
|
@@ -364,8 +600,26 @@ var ACTIONS = [
|
|
|
364
600
|
scopes: ["quotes:read"],
|
|
365
601
|
input: [
|
|
366
602
|
{ name: "status", type: "string", required: false, description: "Filter by quote status." },
|
|
603
|
+
{
|
|
604
|
+
name: "createdAfter",
|
|
605
|
+
type: "string",
|
|
606
|
+
required: false,
|
|
607
|
+
description: "ISO 8601 lower bound on creation time, exclusive. For a single day, pass that day's midnight and the next day's midnight."
|
|
608
|
+
},
|
|
609
|
+
{
|
|
610
|
+
name: "createdBefore",
|
|
611
|
+
type: "string",
|
|
612
|
+
required: false,
|
|
613
|
+
description: "ISO 8601 upper bound on creation time, exclusive."
|
|
614
|
+
},
|
|
367
615
|
{ name: "limit", type: "number", required: false, description: "Page size." },
|
|
368
|
-
{ name: "cursor", type: "string", required: false, description: "Pagination cursor." }
|
|
616
|
+
{ name: "cursor", type: "string", required: false, description: "Pagination cursor." },
|
|
617
|
+
{
|
|
618
|
+
name: "applicationId",
|
|
619
|
+
type: "string",
|
|
620
|
+
required: false,
|
|
621
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
622
|
+
}
|
|
369
623
|
],
|
|
370
624
|
requests: [{ method: "GET", path: "/api/v1/quotes" }],
|
|
371
625
|
channels: ["mcp", "zapier", "make", "n8n"]
|
|
@@ -373,12 +627,21 @@ var ACTIONS = [
|
|
|
373
627
|
{
|
|
374
628
|
id: "get_quote",
|
|
375
629
|
title: "Get one quote",
|
|
376
|
-
description: "Read a single quote with its line items
|
|
630
|
+
description: "Read a single quote: status, totals, and each revision with its line items and payment terms.",
|
|
377
631
|
kind: "read",
|
|
378
632
|
scopes: ["quotes:read"],
|
|
379
|
-
input: [
|
|
633
|
+
input: [
|
|
634
|
+
{ name: "id", type: "string", required: true, description: "Quote id." },
|
|
635
|
+
{
|
|
636
|
+
name: "applicationId",
|
|
637
|
+
type: "string",
|
|
638
|
+
required: false,
|
|
639
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
640
|
+
}
|
|
641
|
+
],
|
|
380
642
|
requests: [{ method: "GET", path: "/api/v1/quotes/:id" }],
|
|
381
|
-
channels: ["mcp", "zapier", "make", "n8n"]
|
|
643
|
+
channels: ["mcp", "zapier", "make", "n8n"],
|
|
644
|
+
guardrails: [OAUTH_WITHHELD]
|
|
382
645
|
},
|
|
383
646
|
// ----------------------------------------------------------------- invoices
|
|
384
647
|
{
|
|
@@ -390,10 +653,17 @@ var ACTIONS = [
|
|
|
390
653
|
input: [
|
|
391
654
|
{ name: "status", type: "string", required: false, description: "Filter by invoice status." },
|
|
392
655
|
{ name: "limit", type: "number", required: false, description: "Page size." },
|
|
393
|
-
{ name: "cursor", type: "string", required: false, description: "Pagination cursor." }
|
|
656
|
+
{ name: "cursor", type: "string", required: false, description: "Pagination cursor." },
|
|
657
|
+
{
|
|
658
|
+
name: "applicationId",
|
|
659
|
+
type: "string",
|
|
660
|
+
required: false,
|
|
661
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
662
|
+
}
|
|
394
663
|
],
|
|
395
664
|
requests: [{ method: "GET", path: "/api/v1/invoices" }],
|
|
396
|
-
channels: ["mcp", "zapier", "make", "n8n"]
|
|
665
|
+
channels: ["mcp", "zapier", "make", "n8n"],
|
|
666
|
+
guardrails: [OAUTH_WITHHELD]
|
|
397
667
|
},
|
|
398
668
|
{
|
|
399
669
|
id: "receivables_aging",
|
|
@@ -401,7 +671,14 @@ var ACTIONS = [
|
|
|
401
671
|
description: 'Outstanding receivables bucketed by age \u2014 answers "what is overdue and by how long".',
|
|
402
672
|
kind: "read",
|
|
403
673
|
scopes: ["invoices:read"],
|
|
404
|
-
input: [
|
|
674
|
+
input: [
|
|
675
|
+
{
|
|
676
|
+
name: "applicationId",
|
|
677
|
+
type: "string",
|
|
678
|
+
required: false,
|
|
679
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
680
|
+
}
|
|
681
|
+
],
|
|
405
682
|
requests: [{ method: "GET", path: "/api/v1/invoices/aging" }],
|
|
406
683
|
channels: ["mcp", "zapier", "make", "n8n"]
|
|
407
684
|
},
|
|
@@ -426,7 +703,13 @@ var ACTIONS = [
|
|
|
426
703
|
{ name: "createdAfter", type: "string", required: false, description: "ISO 8601 date lower bound." },
|
|
427
704
|
{ name: "createdBefore", type: "string", required: false, description: "ISO 8601 date upper bound." },
|
|
428
705
|
{ name: "limit", type: "number", required: false, description: "Page size." },
|
|
429
|
-
{ name: "cursor", type: "string", required: false, description: "Pagination cursor." }
|
|
706
|
+
{ name: "cursor", type: "string", required: false, description: "Pagination cursor." },
|
|
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
|
+
}
|
|
430
713
|
],
|
|
431
714
|
requests: [{ method: "GET", path: "/api/v1/discounts" }],
|
|
432
715
|
channels: ["mcp", "zapier", "make", "n8n"]
|
|
@@ -437,7 +720,15 @@ var ACTIONS = [
|
|
|
437
720
|
description: "Read a single discount code: value, conditions, usage limits and redemption count.",
|
|
438
721
|
kind: "read",
|
|
439
722
|
scopes: ["discounts:read"],
|
|
440
|
-
input: [
|
|
723
|
+
input: [
|
|
724
|
+
{ name: "id", type: "string", required: true, description: "Discount id." },
|
|
725
|
+
{
|
|
726
|
+
name: "applicationId",
|
|
727
|
+
type: "string",
|
|
728
|
+
required: false,
|
|
729
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
730
|
+
}
|
|
731
|
+
],
|
|
441
732
|
requests: [{ method: "GET", path: "/api/v1/discounts/:id" }],
|
|
442
733
|
channels: ["mcp", "zapier", "make", "n8n"]
|
|
443
734
|
},
|
|
@@ -464,7 +755,13 @@ var ACTIONS = [
|
|
|
464
755
|
{ name: "amountMin", type: "number", required: false, description: "Minimum amount, minor units." },
|
|
465
756
|
{ name: "amountMax", type: "number", required: false, description: "Maximum amount, minor units." },
|
|
466
757
|
{ name: "limit", type: "number", required: false, description: "Page size." },
|
|
467
|
-
{ name: "cursor", type: "string", required: false, description: "Pagination cursor." }
|
|
758
|
+
{ name: "cursor", type: "string", required: false, description: "Pagination cursor." },
|
|
759
|
+
{
|
|
760
|
+
name: "applicationId",
|
|
761
|
+
type: "string",
|
|
762
|
+
required: false,
|
|
763
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
764
|
+
}
|
|
468
765
|
],
|
|
469
766
|
requests: [{ method: "GET", path: "/api/v1/payments" }],
|
|
470
767
|
channels: ["mcp", "zapier", "make", "n8n"]
|
|
@@ -475,7 +772,15 @@ var ACTIONS = [
|
|
|
475
772
|
description: "Read a single payment plus its full transaction history (authorize/capture/refund/void attempts).",
|
|
476
773
|
kind: "read",
|
|
477
774
|
scopes: ["payments:read"],
|
|
478
|
-
input: [
|
|
775
|
+
input: [
|
|
776
|
+
{ name: "id", type: "string", required: true, description: "Payment id." },
|
|
777
|
+
{
|
|
778
|
+
name: "applicationId",
|
|
779
|
+
type: "string",
|
|
780
|
+
required: false,
|
|
781
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
782
|
+
}
|
|
783
|
+
],
|
|
479
784
|
requests: [
|
|
480
785
|
{ method: "GET", path: "/api/v1/payments/:id" },
|
|
481
786
|
{ method: "GET", path: "/api/v1/payments/:id/transactions" }
|
|
@@ -492,9 +797,18 @@ var ACTIONS = [
|
|
|
492
797
|
description: "Read a single issued invoice: line items, totals, status and the billing snapshot at issue time.",
|
|
493
798
|
kind: "read",
|
|
494
799
|
scopes: ["invoices:read"],
|
|
495
|
-
input: [
|
|
800
|
+
input: [
|
|
801
|
+
{ name: "id", type: "string", required: true, description: "Invoice id." },
|
|
802
|
+
{
|
|
803
|
+
name: "applicationId",
|
|
804
|
+
type: "string",
|
|
805
|
+
required: false,
|
|
806
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
807
|
+
}
|
|
808
|
+
],
|
|
496
809
|
requests: [{ method: "GET", path: "/api/v1/invoices/:id" }],
|
|
497
|
-
channels: ["mcp", "zapier", "make", "n8n"]
|
|
810
|
+
channels: ["mcp", "zapier", "make", "n8n"],
|
|
811
|
+
guardrails: [OAUTH_WITHHELD]
|
|
498
812
|
},
|
|
499
813
|
// ------------------------------------------------------------------ carts
|
|
500
814
|
{
|
|
@@ -506,7 +820,13 @@ var ACTIONS = [
|
|
|
506
820
|
input: [
|
|
507
821
|
{ name: "sinceDays", type: "number", required: false, description: "How many days back to look." },
|
|
508
822
|
{ name: "limit", type: "number", required: false, description: "Page size." },
|
|
509
|
-
{ name: "cursor", type: "string", required: false, description: "Pagination cursor." }
|
|
823
|
+
{ name: "cursor", type: "string", required: false, description: "Pagination cursor." },
|
|
824
|
+
{
|
|
825
|
+
name: "applicationId",
|
|
826
|
+
type: "string",
|
|
827
|
+
required: false,
|
|
828
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
829
|
+
}
|
|
510
830
|
],
|
|
511
831
|
requests: [
|
|
512
832
|
{ method: "GET", path: "/api/v1/abandoned-carts" },
|
|
@@ -527,7 +847,13 @@ var ACTIONS = [
|
|
|
527
847
|
{ name: "entityType", type: "string", required: false, description: "Entity type filter." },
|
|
528
848
|
{ name: "entityId", type: "string", required: false, description: "Entity id filter." },
|
|
529
849
|
{ name: "limit", type: "number", required: false, description: "Page size." },
|
|
530
|
-
{ name: "cursor", type: "string", required: false, description: "Pagination cursor." }
|
|
850
|
+
{ name: "cursor", type: "string", required: false, description: "Pagination cursor." },
|
|
851
|
+
{
|
|
852
|
+
name: "applicationId",
|
|
853
|
+
type: "string",
|
|
854
|
+
required: false,
|
|
855
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
856
|
+
}
|
|
531
857
|
],
|
|
532
858
|
requests: [{ method: "GET", path: "/api/v1/events" }],
|
|
533
859
|
channels: ["mcp", "n8n"]
|
|
@@ -535,13 +861,30 @@ var ACTIONS = [
|
|
|
535
861
|
{
|
|
536
862
|
id: "list_webhook_deliveries",
|
|
537
863
|
title: "List webhook deliveries",
|
|
538
|
-
description: "Recent outbound webhook deliveries, filterable by endpoint and
|
|
864
|
+
description: "Recent outbound webhook deliveries, filterable by endpoint, status and event type.",
|
|
539
865
|
kind: "read",
|
|
540
866
|
scopes: ["webhook_deliveries:read"],
|
|
541
867
|
input: [
|
|
542
868
|
{ name: "endpointId", type: "string", required: false, description: "Filter by endpoint." },
|
|
543
|
-
{
|
|
544
|
-
|
|
869
|
+
{
|
|
870
|
+
name: "status",
|
|
871
|
+
type: "string",
|
|
872
|
+
required: false,
|
|
873
|
+
description: "Filter by delivery status: pending, delivered, failed or dead_letter."
|
|
874
|
+
},
|
|
875
|
+
{
|
|
876
|
+
name: "eventType",
|
|
877
|
+
type: "string",
|
|
878
|
+
required: false,
|
|
879
|
+
description: "Filter by exact event type, e.g. payment.failed."
|
|
880
|
+
},
|
|
881
|
+
{ name: "limit", type: "number", required: false, description: "Page size." },
|
|
882
|
+
{
|
|
883
|
+
name: "applicationId",
|
|
884
|
+
type: "string",
|
|
885
|
+
required: false,
|
|
886
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
887
|
+
}
|
|
545
888
|
],
|
|
546
889
|
requests: [{ method: "GET", path: "/api/v1/webhook-deliveries" }],
|
|
547
890
|
channels: ["mcp", "n8n"]
|
|
@@ -552,7 +895,15 @@ var ACTIONS = [
|
|
|
552
895
|
description: "Full delivery record: the signed payload we sent, the response we got back, and every attempt.",
|
|
553
896
|
kind: "read",
|
|
554
897
|
scopes: ["webhook_deliveries:read"],
|
|
555
|
-
input: [
|
|
898
|
+
input: [
|
|
899
|
+
{ name: "id", type: "string", required: true, description: "Delivery id." },
|
|
900
|
+
{
|
|
901
|
+
name: "applicationId",
|
|
902
|
+
type: "string",
|
|
903
|
+
required: false,
|
|
904
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
905
|
+
}
|
|
906
|
+
],
|
|
556
907
|
requests: [{ method: "GET", path: "/api/v1/webhook-deliveries/:id" }],
|
|
557
908
|
channels: ["mcp", "n8n"]
|
|
558
909
|
},
|
|
@@ -562,12 +913,19 @@ var ACTIONS = [
|
|
|
562
913
|
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.",
|
|
563
914
|
kind: "read",
|
|
564
915
|
scopes: ["checkout_sessions:read"],
|
|
916
|
+
sectionScopes: ["events:read", "webhook_deliveries:read"],
|
|
565
917
|
input: [
|
|
566
918
|
{
|
|
567
919
|
name: "id",
|
|
568
920
|
type: "string",
|
|
569
921
|
required: true,
|
|
570
922
|
description: "Checkout session id (or cart id \u2014 the tool resolves the session)."
|
|
923
|
+
},
|
|
924
|
+
{
|
|
925
|
+
name: "applicationId",
|
|
926
|
+
type: "string",
|
|
927
|
+
required: false,
|
|
928
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
571
929
|
}
|
|
572
930
|
],
|
|
573
931
|
requests: [
|
|
@@ -578,7 +936,8 @@ var ACTIONS = [
|
|
|
578
936
|
],
|
|
579
937
|
channels: ["mcp"],
|
|
580
938
|
guardrails: [
|
|
581
|
-
"Report which sections were unavailable rather than failing the call when a sub-read is unscoped."
|
|
939
|
+
"Report which sections were unavailable rather than failing the call when a sub-read is unscoped.",
|
|
940
|
+
OAUTH_WITHHELD
|
|
582
941
|
]
|
|
583
942
|
},
|
|
584
943
|
// ------------------------------------------------------------- extensions
|
|
@@ -597,7 +956,13 @@ var ACTIONS = [
|
|
|
597
956
|
enum: ["active", "suspended", "uninstalled"]
|
|
598
957
|
},
|
|
599
958
|
{ name: "limit", type: "number", required: false, description: "Page size." },
|
|
600
|
-
{ name: "cursor", type: "string", required: false, description: "Pagination cursor." }
|
|
959
|
+
{ name: "cursor", type: "string", required: false, description: "Pagination cursor." },
|
|
960
|
+
{
|
|
961
|
+
name: "applicationId",
|
|
962
|
+
type: "string",
|
|
963
|
+
required: false,
|
|
964
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
965
|
+
}
|
|
601
966
|
],
|
|
602
967
|
requests: [{ method: "GET", path: "/api/v1/installations" }],
|
|
603
968
|
channels: ["mcp", "zapier", "make", "n8n"]
|
|
@@ -609,7 +974,14 @@ var ACTIONS = [
|
|
|
609
974
|
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.",
|
|
610
975
|
kind: "read",
|
|
611
976
|
scopes: ["connectors:read"],
|
|
612
|
-
input: [
|
|
977
|
+
input: [
|
|
978
|
+
{
|
|
979
|
+
name: "applicationId",
|
|
980
|
+
type: "string",
|
|
981
|
+
required: false,
|
|
982
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
983
|
+
}
|
|
984
|
+
],
|
|
613
985
|
requests: [
|
|
614
986
|
{ method: "GET", path: "/api/v1/payment-routing" },
|
|
615
987
|
{ method: "GET", path: "/api/v1/routing-connectors" },
|
|
@@ -627,7 +999,15 @@ var ACTIONS = [
|
|
|
627
999
|
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.',
|
|
628
1000
|
kind: "read",
|
|
629
1001
|
scopes: [],
|
|
630
|
-
|
|
1002
|
+
sectionScopes: ["connectors:read", "webhooks:read"],
|
|
1003
|
+
input: [
|
|
1004
|
+
{
|
|
1005
|
+
name: "applicationId",
|
|
1006
|
+
type: "string",
|
|
1007
|
+
required: false,
|
|
1008
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
1009
|
+
}
|
|
1010
|
+
],
|
|
631
1011
|
requests: [
|
|
632
1012
|
{ method: "GET", path: "/api/v1/whoami" },
|
|
633
1013
|
{ method: "GET", path: "/api/v1/connectors" },
|
|
@@ -643,7 +1023,7 @@ var ACTIONS = [
|
|
|
643
1023
|
{
|
|
644
1024
|
id: "get_workspace_settings",
|
|
645
1025
|
title: "Get workspace settings",
|
|
646
|
-
description: "Read this workspace's identity
|
|
1026
|
+
description: "Read this workspace's identity: name, slug, branding and onboarding state. Payment terms are set per application and environment, not here.",
|
|
647
1027
|
kind: "read",
|
|
648
1028
|
scopes: ["merchant:read"],
|
|
649
1029
|
input: [],
|
|
@@ -662,12 +1042,48 @@ var ACTIONS = [
|
|
|
662
1042
|
requests: [{ method: "GET", path: "/api/v1/audit-log" }],
|
|
663
1043
|
channels: ["mcp", "zapier", "make", "n8n"]
|
|
664
1044
|
},
|
|
1045
|
+
{
|
|
1046
|
+
id: "list_applications",
|
|
1047
|
+
title: "List applications in this workspace",
|
|
1048
|
+
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.',
|
|
1049
|
+
kind: "read",
|
|
1050
|
+
scopes: ["applications:read"],
|
|
1051
|
+
input: [],
|
|
1052
|
+
requests: [{ method: "GET", path: "/api/v1/stores" }],
|
|
1053
|
+
channels: ["mcp", "zapier", "make", "n8n"]
|
|
1054
|
+
},
|
|
1055
|
+
{
|
|
1056
|
+
id: "get_sales_timeseries",
|
|
1057
|
+
title: "Sales over time",
|
|
1058
|
+
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.`,
|
|
1059
|
+
kind: "read",
|
|
1060
|
+
scopes: ["orders:read"],
|
|
1061
|
+
input: [
|
|
1062
|
+
{ name: "from", type: "string", required: false, description: 'ISO 8601 start of range, exclusive. Defaults to 30 days before "to".' },
|
|
1063
|
+
{ name: "to", type: "string", required: false, description: "ISO 8601 end of range, exclusive. Defaults to now." },
|
|
1064
|
+
{ name: "groupBy", type: "string", required: false, description: "Bucket size.", enum: ["day", "week", "month"], default: "day" },
|
|
1065
|
+
{ 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." },
|
|
1066
|
+
{ name: "compareTo", type: "string", required: false, description: "Add a comparison total.", enum: ["previous_period", "previous_year", "none"], default: "none" },
|
|
1067
|
+
{
|
|
1068
|
+
name: "applicationId",
|
|
1069
|
+
type: "string",
|
|
1070
|
+
required: false,
|
|
1071
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
1072
|
+
}
|
|
1073
|
+
],
|
|
1074
|
+
requests: [{ method: "GET", path: "/api/v1/analytics/orders-timeseries" }],
|
|
1075
|
+
channels: ["mcp", "zapier", "make", "n8n"],
|
|
1076
|
+
guardrails: [
|
|
1077
|
+
"Revenue is GROSS. Refunds are not deducted, because that needs payment_refunds:read, which OAuth connections cannot hold. Never describe the figure as net."
|
|
1078
|
+
]
|
|
1079
|
+
},
|
|
665
1080
|
{
|
|
666
1081
|
id: "get_business_summary",
|
|
667
1082
|
title: "Business summary",
|
|
668
|
-
description: "How are we doing? Orders/revenue, subscription MRR, receivables and quotes pipeline
|
|
1083
|
+
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.",
|
|
669
1084
|
kind: "read",
|
|
670
1085
|
scopes: [],
|
|
1086
|
+
sectionScopes: ["orders:read", "subscriptions:read", "invoices:read", "quotes:read"],
|
|
671
1087
|
input: [
|
|
672
1088
|
{
|
|
673
1089
|
name: "period",
|
|
@@ -676,6 +1092,15 @@ var ACTIONS = [
|
|
|
676
1092
|
description: "Reporting window.",
|
|
677
1093
|
enum: ["week", "month", "quarter"],
|
|
678
1094
|
default: "month"
|
|
1095
|
+
},
|
|
1096
|
+
{ name: "from", type: "string", required: false, description: "ISO 8601 start of range. Overrides period." },
|
|
1097
|
+
{ name: "to", type: "string", required: false, description: "ISO 8601 end of range. Defaults to now." },
|
|
1098
|
+
{ name: "timezone", type: "string", required: false, description: "IANA timezone for period boundaries. Defaults to UTC." },
|
|
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."
|
|
679
1104
|
}
|
|
680
1105
|
],
|
|
681
1106
|
requests: [{ method: "GET", path: "/api/v1/analytics/business-summary" }],
|
|
@@ -684,19 +1109,189 @@ var ACTIONS = [
|
|
|
684
1109
|
"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."
|
|
685
1110
|
]
|
|
686
1111
|
},
|
|
1112
|
+
{
|
|
1113
|
+
id: "get_top_products",
|
|
1114
|
+
title: "Top products",
|
|
1115
|
+
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.",
|
|
1116
|
+
kind: "read",
|
|
1117
|
+
scopes: ["orders:read"],
|
|
1118
|
+
input: [
|
|
1119
|
+
{ name: "from", type: "string", required: false, description: 'ISO 8601 start of range, exclusive. Defaults to 30 days before "to".' },
|
|
1120
|
+
{ name: "to", type: "string", required: false, description: "ISO 8601 end of range, exclusive. Defaults to now." },
|
|
1121
|
+
{ name: "timezone", type: "string", required: false, description: "IANA timezone for range boundaries. Defaults to UTC." },
|
|
1122
|
+
{ name: "by", type: "string", required: false, description: "Sort key.", enum: ["revenue", "units"], default: "revenue" },
|
|
1123
|
+
{ name: "limit", type: "number", required: false, description: "Max rows to return (1-100). Defaults to 20." },
|
|
1124
|
+
{
|
|
1125
|
+
name: "applicationId",
|
|
1126
|
+
type: "string",
|
|
1127
|
+
required: false,
|
|
1128
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
1129
|
+
}
|
|
1130
|
+
],
|
|
1131
|
+
requests: [{ method: "GET", path: "/api/v1/analytics/products" }],
|
|
1132
|
+
channels: ["mcp", "zapier", "make", "n8n"],
|
|
1133
|
+
guardrails: [
|
|
1134
|
+
"Revenue is GROSS. Refunds are not deducted, because that needs payment_refunds:read, which OAuth connections cannot hold. Never describe the figure as net."
|
|
1135
|
+
]
|
|
1136
|
+
},
|
|
687
1137
|
{
|
|
688
1138
|
id: "get_attention_digest",
|
|
689
1139
|
title: "Attention digest",
|
|
690
1140
|
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.",
|
|
691
1141
|
kind: "read",
|
|
692
1142
|
scopes: [],
|
|
693
|
-
|
|
1143
|
+
sectionScopes: ["payment_disputes:read", "subscriptions:read", "invoices:read", "webhook_deliveries:read"],
|
|
1144
|
+
input: [
|
|
1145
|
+
{
|
|
1146
|
+
name: "applicationId",
|
|
1147
|
+
type: "string",
|
|
1148
|
+
required: false,
|
|
1149
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
1150
|
+
}
|
|
1151
|
+
],
|
|
694
1152
|
requests: [{ method: "GET", path: "/api/v1/analytics/attention-digest" }],
|
|
695
1153
|
channels: ["mcp", "zapier", "make", "n8n"],
|
|
696
1154
|
guardrails: [
|
|
697
1155
|
"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."
|
|
698
1156
|
]
|
|
699
1157
|
},
|
|
1158
|
+
{
|
|
1159
|
+
id: "get_customer_growth",
|
|
1160
|
+
title: "Customer growth",
|
|
1161
|
+
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.",
|
|
1162
|
+
kind: "read",
|
|
1163
|
+
scopes: ["customers:read"],
|
|
1164
|
+
input: [
|
|
1165
|
+
{ name: "from", type: "string", required: false, description: 'ISO 8601 start of range, exclusive. Defaults to 30 days before "to".' },
|
|
1166
|
+
{ name: "to", type: "string", required: false, description: "ISO 8601 end of range, exclusive. Defaults to now." },
|
|
1167
|
+
{ name: "groupBy", type: "string", required: false, description: "Signup series bucket size.", enum: ["day", "week", "month"], default: "day" },
|
|
1168
|
+
{ name: "timezone", type: "string", required: false, description: "IANA timezone for bucket boundaries. Defaults to UTC." },
|
|
1169
|
+
{ name: "topBy", type: "string", required: false, description: "Sort key for top customers.", enum: ["revenue", "orders"], default: "revenue" },
|
|
1170
|
+
{ name: "topLimit", type: "number", required: false, description: "Max top-customers rows to return (1-50). Defaults to 10." },
|
|
1171
|
+
{
|
|
1172
|
+
name: "applicationId",
|
|
1173
|
+
type: "string",
|
|
1174
|
+
required: false,
|
|
1175
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
1176
|
+
}
|
|
1177
|
+
],
|
|
1178
|
+
requests: [{ method: "GET", path: "/api/v1/analytics/customers" }],
|
|
1179
|
+
channels: ["mcp", "zapier", "make", "n8n"],
|
|
1180
|
+
guardrails: [
|
|
1181
|
+
"Revenue is GROSS. Refunds are not deducted, because that needs payment_refunds:read, which OAuth connections cannot hold. Never describe the figure as net.",
|
|
1182
|
+
"topCustomers is a top-N list; use the accompanying totals for range-wide customer/order/revenue counts instead of summing the returned rows.",
|
|
1183
|
+
"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."
|
|
1184
|
+
]
|
|
1185
|
+
},
|
|
1186
|
+
{
|
|
1187
|
+
id: "get_revenue_breakdown",
|
|
1188
|
+
title: "Revenue breakdown",
|
|
1189
|
+
description: "Revenue split by one dimension over a date range \u2014 payment method, order source, or currency. Amounts are gross; refunds are not deducted.",
|
|
1190
|
+
kind: "read",
|
|
1191
|
+
scopes: ["orders:read"],
|
|
1192
|
+
input: [
|
|
1193
|
+
{
|
|
1194
|
+
name: "dimension",
|
|
1195
|
+
type: "string",
|
|
1196
|
+
required: true,
|
|
1197
|
+
description: "Breakdown dimension.",
|
|
1198
|
+
enum: ["paymentMethod", "source", "currency"]
|
|
1199
|
+
},
|
|
1200
|
+
{ name: "from", type: "string", required: false, description: 'ISO 8601 start of range, exclusive. Defaults to 30 days before "to".' },
|
|
1201
|
+
{ name: "to", type: "string", required: false, description: "ISO 8601 end of range, exclusive. Defaults to now." },
|
|
1202
|
+
{ name: "timezone", type: "string", required: false, description: "IANA timezone for range boundaries. Defaults to UTC." },
|
|
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/revenue-breakdown" }],
|
|
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
|
+
"dimension is required; it is never defaulted, so omitting it fails the call instead of silently picking a dimension.",
|
|
1215
|
+
"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)."
|
|
1216
|
+
]
|
|
1217
|
+
},
|
|
1218
|
+
{
|
|
1219
|
+
id: "get_funnel_conversion",
|
|
1220
|
+
title: "Checkout funnel",
|
|
1221
|
+
description: "Checkout funnel over a date range: carts created, checkout sessions started, orders completed, with the drop-off between each stage.",
|
|
1222
|
+
kind: "read",
|
|
1223
|
+
scopes: ["carts:read"],
|
|
1224
|
+
input: [
|
|
1225
|
+
{ name: "from", type: "string", required: false, description: 'ISO 8601 start of range, exclusive. Defaults to 30 days before "to".' },
|
|
1226
|
+
{ name: "to", type: "string", required: false, description: "ISO 8601 end of range, exclusive. Defaults to now." },
|
|
1227
|
+
{ name: "timezone", type: "string", required: false, description: "IANA timezone for range boundaries. Defaults to UTC." },
|
|
1228
|
+
{
|
|
1229
|
+
name: "applicationId",
|
|
1230
|
+
type: "string",
|
|
1231
|
+
required: false,
|
|
1232
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
1233
|
+
}
|
|
1234
|
+
],
|
|
1235
|
+
requests: [{ method: "GET", path: "/api/v1/analytics/funnel" }],
|
|
1236
|
+
channels: ["mcp", "zapier", "make", "n8n"],
|
|
1237
|
+
guardrails: [
|
|
1238
|
+
"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.",
|
|
1239
|
+
"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.",
|
|
1240
|
+
'dropOff[].ratePct is the percentage of "from" that did NOT reach "to" (loss), not a conversion rate. The step-conversion rate is 100 - ratePct.'
|
|
1241
|
+
]
|
|
1242
|
+
},
|
|
1243
|
+
{
|
|
1244
|
+
id: "get_subscription_movement",
|
|
1245
|
+
title: "Subscription movement",
|
|
1246
|
+
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.",
|
|
1247
|
+
kind: "read",
|
|
1248
|
+
scopes: ["subscriptions:read"],
|
|
1249
|
+
input: [
|
|
1250
|
+
{ name: "from", type: "string", required: false, description: 'ISO 8601 start of range, exclusive. Defaults to 30 days before "to".' },
|
|
1251
|
+
{ name: "to", type: "string", required: false, description: "ISO 8601 end of range, exclusive. Defaults to now." },
|
|
1252
|
+
{ name: "timezone", type: "string", required: false, description: "IANA timezone for bucket boundaries. Defaults to UTC." },
|
|
1253
|
+
{ name: "groupBy", type: "string", required: false, description: "Bucket size.", enum: ["day", "week", "month"], default: "day" },
|
|
1254
|
+
{
|
|
1255
|
+
name: "applicationId",
|
|
1256
|
+
type: "string",
|
|
1257
|
+
required: false,
|
|
1258
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
1259
|
+
}
|
|
1260
|
+
],
|
|
1261
|
+
requests: [{ method: "GET", path: "/api/v1/analytics/subscriptions" }],
|
|
1262
|
+
channels: ["mcp", "zapier", "make", "n8n"],
|
|
1263
|
+
guardrails: [
|
|
1264
|
+
"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.",
|
|
1265
|
+
"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.",
|
|
1266
|
+
"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[]."
|
|
1267
|
+
]
|
|
1268
|
+
},
|
|
1269
|
+
{
|
|
1270
|
+
id: "get_discount_performance",
|
|
1271
|
+
title: "Discount performance",
|
|
1272
|
+
description: "Discount code performance over a date range: redemption count, revenue on discounted orders, and total discount given per code.",
|
|
1273
|
+
kind: "read",
|
|
1274
|
+
scopes: ["discounts:read"],
|
|
1275
|
+
input: [
|
|
1276
|
+
{ name: "from", type: "string", required: false, description: 'ISO 8601 start of range, exclusive. Defaults to 30 days before "to".' },
|
|
1277
|
+
{ name: "to", type: "string", required: false, description: "ISO 8601 end of range, exclusive. Defaults to now." },
|
|
1278
|
+
{ name: "timezone", type: "string", required: false, description: "IANA timezone for range boundaries. Defaults to UTC." },
|
|
1279
|
+
{ name: "limit", type: "number", required: false, description: "Max rows to return. Defaults to 20." },
|
|
1280
|
+
{
|
|
1281
|
+
name: "applicationId",
|
|
1282
|
+
type: "string",
|
|
1283
|
+
required: false,
|
|
1284
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
1285
|
+
}
|
|
1286
|
+
],
|
|
1287
|
+
requests: [{ method: "GET", path: "/api/v1/analytics/discounts" }],
|
|
1288
|
+
channels: ["mcp", "zapier", "make", "n8n"],
|
|
1289
|
+
guardrails: [
|
|
1290
|
+
'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.',
|
|
1291
|
+
"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.",
|
|
1292
|
+
"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."
|
|
1293
|
+
]
|
|
1294
|
+
},
|
|
700
1295
|
// ------------------------------------------------------------ sandbox write
|
|
701
1296
|
{
|
|
702
1297
|
id: "seed_test_data",
|
|
@@ -722,6 +1317,151 @@ var ACTIONS = [
|
|
|
722
1317
|
"Seeds a finalized DRAFT order \u2014 it does not capture a payment, which needs a browser-side tokenised payment method."
|
|
723
1318
|
]
|
|
724
1319
|
},
|
|
1320
|
+
// ---------------------------------------------------------- inbound writes
|
|
1321
|
+
// The no-code channels are mostly used in the "something happened elsewhere,
|
|
1322
|
+
// put it into Throttle" direction — a form submission, a new CRM contact, a
|
|
1323
|
+
// spreadsheet row. These three are that direction.
|
|
1324
|
+
{
|
|
1325
|
+
id: "create_customer",
|
|
1326
|
+
title: "Create a customer",
|
|
1327
|
+
description: "Create a customer record. Use search_customers first if the buyer may already exist \u2014 this does not de-duplicate by email.",
|
|
1328
|
+
kind: "write",
|
|
1329
|
+
scopes: ["customers:write"],
|
|
1330
|
+
input: [
|
|
1331
|
+
{ name: "email", type: "string", required: true, description: "The buyer's email address." },
|
|
1332
|
+
{ name: "firstName", type: "string", required: false, description: "Given name." },
|
|
1333
|
+
{ name: "lastName", type: "string", required: false, description: "Family name." },
|
|
1334
|
+
{ name: "phone", type: "string", required: false, description: "Contact phone number." },
|
|
1335
|
+
{ name: "company", type: "string", required: false, description: "The buyer's company." },
|
|
1336
|
+
{
|
|
1337
|
+
name: "externalId",
|
|
1338
|
+
type: "string",
|
|
1339
|
+
required: false,
|
|
1340
|
+
description: "This customer's id in your own system. Resolvable later via get_customer."
|
|
1341
|
+
},
|
|
1342
|
+
{
|
|
1343
|
+
name: "netN",
|
|
1344
|
+
type: "number",
|
|
1345
|
+
required: false,
|
|
1346
|
+
description: "Invoice terms in days for this customer specifically, 0-365. Omit to use the workspace default; 0 means due on issue."
|
|
1347
|
+
},
|
|
1348
|
+
{
|
|
1349
|
+
name: "acceptsMarketing",
|
|
1350
|
+
type: "boolean",
|
|
1351
|
+
required: false,
|
|
1352
|
+
description: "Whether the buyer opted into marketing email.",
|
|
1353
|
+
default: false
|
|
1354
|
+
}
|
|
1355
|
+
],
|
|
1356
|
+
requests: [{ method: "POST", path: "/api/v1/customers" }],
|
|
1357
|
+
channels: ["mcp", "zapier", "make", "n8n"],
|
|
1358
|
+
idempotent: true,
|
|
1359
|
+
guardrails: [
|
|
1360
|
+
"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."
|
|
1361
|
+
]
|
|
1362
|
+
},
|
|
1363
|
+
{
|
|
1364
|
+
id: "update_customer",
|
|
1365
|
+
title: "Update a customer",
|
|
1366
|
+
description: "Change fields on an existing customer. Only the fields supplied are touched; everything else is left alone.",
|
|
1367
|
+
kind: "write",
|
|
1368
|
+
scopes: ["customers:write"],
|
|
1369
|
+
input: [
|
|
1370
|
+
{ name: "id", type: "string", required: true, description: "Customer id." },
|
|
1371
|
+
{ name: "email", type: "string", required: false, description: "The buyer's email address." },
|
|
1372
|
+
{ name: "firstName", type: "string", required: false, description: "Given name." },
|
|
1373
|
+
{ name: "lastName", type: "string", required: false, description: "Family name." },
|
|
1374
|
+
{ name: "phone", type: "string", required: false, description: "Contact phone number." },
|
|
1375
|
+
{ name: "company", type: "string", required: false, description: "The buyer's company." },
|
|
1376
|
+
{
|
|
1377
|
+
name: "externalId",
|
|
1378
|
+
type: "string",
|
|
1379
|
+
required: false,
|
|
1380
|
+
description: "This customer's id in your own system. Pass null to clear it."
|
|
1381
|
+
},
|
|
1382
|
+
{
|
|
1383
|
+
name: "netN",
|
|
1384
|
+
type: "number",
|
|
1385
|
+
required: false,
|
|
1386
|
+
description: "Invoice terms in days for this customer specifically, 0-365."
|
|
1387
|
+
},
|
|
1388
|
+
{
|
|
1389
|
+
name: "acceptsMarketing",
|
|
1390
|
+
type: "boolean",
|
|
1391
|
+
required: false,
|
|
1392
|
+
description: "Whether the buyer opted into marketing email."
|
|
1393
|
+
}
|
|
1394
|
+
],
|
|
1395
|
+
requests: [{ method: "PATCH", path: "/api/v1/customers/:id" }],
|
|
1396
|
+
channels: ["mcp", "zapier", "make", "n8n"],
|
|
1397
|
+
idempotent: true,
|
|
1398
|
+
guardrails: [
|
|
1399
|
+
"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."
|
|
1400
|
+
]
|
|
1401
|
+
},
|
|
1402
|
+
{
|
|
1403
|
+
id: "create_checkout_link",
|
|
1404
|
+
title: "Create a checkout link",
|
|
1405
|
+
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).",
|
|
1406
|
+
kind: "write",
|
|
1407
|
+
scopes: ["carts:write", "cart_items:write", "checkout_sessions:write"],
|
|
1408
|
+
input: [
|
|
1409
|
+
{
|
|
1410
|
+
name: "lineItems",
|
|
1411
|
+
type: "array",
|
|
1412
|
+
required: true,
|
|
1413
|
+
description: "What the buyer is paying for: { name, unitPrice, quantity }. unitPrice is an integer in minor units."
|
|
1414
|
+
},
|
|
1415
|
+
{
|
|
1416
|
+
name: "returnUrl",
|
|
1417
|
+
type: "string",
|
|
1418
|
+
required: true,
|
|
1419
|
+
description: "Absolute URL the buyer lands on after paying."
|
|
1420
|
+
},
|
|
1421
|
+
{
|
|
1422
|
+
name: "cancelUrl",
|
|
1423
|
+
type: "string",
|
|
1424
|
+
required: true,
|
|
1425
|
+
description: "Absolute URL the buyer lands on if they abandon checkout."
|
|
1426
|
+
},
|
|
1427
|
+
{
|
|
1428
|
+
name: "customerId",
|
|
1429
|
+
type: "string",
|
|
1430
|
+
required: false,
|
|
1431
|
+
description: "Attach the cart to an existing customer."
|
|
1432
|
+
},
|
|
1433
|
+
{
|
|
1434
|
+
name: "customerEmail",
|
|
1435
|
+
type: "string",
|
|
1436
|
+
required: false,
|
|
1437
|
+
description: "Pre-fill the buyer email without needing a customer record."
|
|
1438
|
+
},
|
|
1439
|
+
{
|
|
1440
|
+
name: "discountCode",
|
|
1441
|
+
type: "string",
|
|
1442
|
+
required: false,
|
|
1443
|
+
description: "Discount code to apply to the session."
|
|
1444
|
+
},
|
|
1445
|
+
{
|
|
1446
|
+
name: "netN",
|
|
1447
|
+
type: "number",
|
|
1448
|
+
required: false,
|
|
1449
|
+
description: "Invoice terms in days for this cart, 0-365. Omit to pay in full at checkout."
|
|
1450
|
+
}
|
|
1451
|
+
],
|
|
1452
|
+
// Three calls: the API has no single "checkout link" endpoint, and a
|
|
1453
|
+
// checkout session requires a cart that already holds its line items.
|
|
1454
|
+
requests: [
|
|
1455
|
+
{ method: "POST", path: "/api/v1/carts" },
|
|
1456
|
+
{ method: "POST", path: "/api/v1/carts/:id/items" },
|
|
1457
|
+
{ method: "POST", path: "/api/v1/checkout/sessions" }
|
|
1458
|
+
],
|
|
1459
|
+
channels: ["mcp", "zapier", "make", "n8n"],
|
|
1460
|
+
idempotent: true,
|
|
1461
|
+
guardrails: [
|
|
1462
|
+
"The returned URL is buyer-facing and takes real payment in a production environment."
|
|
1463
|
+
]
|
|
1464
|
+
},
|
|
725
1465
|
// -------------------------------------------------------------- money/mail
|
|
726
1466
|
{
|
|
727
1467
|
id: "create_quote",
|
|
@@ -750,7 +1490,8 @@ var ACTIONS = [
|
|
|
750
1490
|
requests: [{ method: "POST", path: "/api/v1/quotes/:id/issue" }],
|
|
751
1491
|
channels: ["mcp", "zapier", "make", "n8n"],
|
|
752
1492
|
idempotent: true,
|
|
753
|
-
guardrails: ['Sends mail to a real buyer. Never call it speculatively or to "check" a quote.']
|
|
1493
|
+
guardrails: ['Sends mail to a real buyer. Never call it speculatively or to "check" a quote.'],
|
|
1494
|
+
destructive: true
|
|
754
1495
|
},
|
|
755
1496
|
{
|
|
756
1497
|
id: "refund_payment",
|
|
@@ -769,12 +1510,20 @@ var ACTIONS = [
|
|
|
769
1510
|
{ name: "reason", type: "string", required: false, description: "Reason recorded on the refund." }
|
|
770
1511
|
],
|
|
771
1512
|
requests: [{ method: "POST", path: "/api/v1/payments/:id/refund" }],
|
|
772
|
-
|
|
1513
|
+
// NOT zapier/make. `payment_refunds:write` is `sensitive` in the scope
|
|
1514
|
+
// registry, and `validateScopesForOAuth` rejects every sensitive scope — so
|
|
1515
|
+
// an OAuth grant, which is the only credential a public Zapier or Make app
|
|
1516
|
+
// can hold, can never carry it. Listing the action on those channels would
|
|
1517
|
+
// ship a connector action that 403s on every call. MCP and n8n keep it:
|
|
1518
|
+
// both authenticate with an `sk_` key, which CAN hold the scope.
|
|
1519
|
+
// `oauth-grantable-scopes` in the catalog tests enforces this.
|
|
1520
|
+
channels: ["mcp", "n8n"],
|
|
773
1521
|
idempotent: true,
|
|
774
1522
|
guardrails: [
|
|
775
1523
|
"Moves money and is irreversible. Confirm with the user first.",
|
|
776
1524
|
"The API treats an omitted amount as a FULL refund, so this action requires it explicitly."
|
|
777
|
-
]
|
|
1525
|
+
],
|
|
1526
|
+
destructive: true
|
|
778
1527
|
},
|
|
779
1528
|
{
|
|
780
1529
|
id: "manage_subscription",
|
|
@@ -817,7 +1566,8 @@ var ACTIONS = [
|
|
|
817
1566
|
requests: [{ method: "POST", path: "/api/v1/subscriptions/:id/cancel" }],
|
|
818
1567
|
channels: ["mcp", "zapier", "make", "n8n"],
|
|
819
1568
|
idempotent: true,
|
|
820
|
-
guardrails: ["Ends billing for a paying customer. Confirm with the user first."]
|
|
1569
|
+
guardrails: ["Ends billing for a paying customer. Confirm with the user first."],
|
|
1570
|
+
destructive: true
|
|
821
1571
|
},
|
|
822
1572
|
{
|
|
823
1573
|
id: "replay_webhook_delivery",
|
|
@@ -829,7 +1579,8 @@ var ACTIONS = [
|
|
|
829
1579
|
requests: [{ method: "POST", path: "/api/v1/webhook-deliveries/:id/replay" }],
|
|
830
1580
|
channels: ["mcp", "n8n"],
|
|
831
1581
|
idempotent: true,
|
|
832
|
-
guardrails: ["The receiving system will process the event again. Confirm it is idempotent first."]
|
|
1582
|
+
guardrails: ["The receiving system will process the event again. Confirm it is idempotent first."],
|
|
1583
|
+
destructive: true
|
|
833
1584
|
},
|
|
834
1585
|
{
|
|
835
1586
|
id: "send_test_webhook",
|
|
@@ -843,7 +1594,8 @@ var ACTIONS = [
|
|
|
843
1594
|
],
|
|
844
1595
|
requests: [{ method: "POST", path: "/api/v1/webhook-endpoints/:id/test" }],
|
|
845
1596
|
channels: ["mcp", "n8n"],
|
|
846
|
-
idempotent: true
|
|
1597
|
+
idempotent: true,
|
|
1598
|
+
destructive: true
|
|
847
1599
|
}
|
|
848
1600
|
];
|
|
849
1601
|
|
|
@@ -944,6 +1696,7 @@ var ThrottleError = class extends Error {
|
|
|
944
1696
|
statusCode;
|
|
945
1697
|
details;
|
|
946
1698
|
body;
|
|
1699
|
+
requestId;
|
|
947
1700
|
constructor(args) {
|
|
948
1701
|
super(args.message);
|
|
949
1702
|
this.name = "ThrottleError";
|
|
@@ -951,14 +1704,17 @@ var ThrottleError = class extends Error {
|
|
|
951
1704
|
this.statusCode = args.statusCode;
|
|
952
1705
|
this.details = args.details;
|
|
953
1706
|
this.body = args.body;
|
|
1707
|
+
const fromBody = args.body?.meta?.requestId;
|
|
1708
|
+
const fromHeader = typeof args.responseHeaders?.get === "function" ? args.responseHeaders.get("x-request-id") : args.responseHeaders?.["x-request-id"];
|
|
1709
|
+
this.requestId = args.requestId ?? (typeof fromBody === "string" ? fromBody : void 0) ?? (typeof fromHeader === "string" ? fromHeader : void 0);
|
|
954
1710
|
Object.setPrototypeOf(this, new.target.prototype);
|
|
955
1711
|
}
|
|
956
1712
|
};
|
|
957
1713
|
|
|
958
1714
|
// src/client.ts
|
|
959
1715
|
var ThrottleApiError = class extends ThrottleError {
|
|
960
|
-
constructor(code, message, statusCode, details) {
|
|
961
|
-
super({ code, message, statusCode, details });
|
|
1716
|
+
constructor(code, message, statusCode, details, responseHeaders) {
|
|
1717
|
+
super({ code, message, statusCode, details, responseHeaders });
|
|
962
1718
|
this.name = "ThrottleApiError";
|
|
963
1719
|
}
|
|
964
1720
|
};
|
|
@@ -972,18 +1728,43 @@ function buildQuery(query) {
|
|
|
972
1728
|
const qs = params.toString();
|
|
973
1729
|
return qs ? `?${qs}` : "";
|
|
974
1730
|
}
|
|
1731
|
+
var API_KEY_PREFIX_RE = /^(sk_|pk_)/;
|
|
975
1732
|
var ThrottleClient = class {
|
|
976
1733
|
constructor(auth, fetchImpl = fetch) {
|
|
977
1734
|
this.auth = auth;
|
|
978
1735
|
this.fetchImpl = fetchImpl;
|
|
979
1736
|
}
|
|
1737
|
+
/**
|
|
1738
|
+
* Swap the credential every later request is sent with. The hosted remote
|
|
1739
|
+
* server builds one client per MCP session at `initialize`, but an OAuth
|
|
1740
|
+
* access token lives one hour and the MCP client refreshes it underneath
|
|
1741
|
+
* the session — each request then arrives with a newer bearer than the one
|
|
1742
|
+
* the session was opened with. Without this, the session keeps calling the
|
|
1743
|
+
* REST API with the token it was born with and every tool fails with a 401
|
|
1744
|
+
* the moment that first token expires, even though the caller is still
|
|
1745
|
+
* perfectly authenticated.
|
|
1746
|
+
*/
|
|
1747
|
+
setApiKey(apiKey) {
|
|
1748
|
+
this.auth = { ...this.auth, apiKey };
|
|
1749
|
+
}
|
|
980
1750
|
async requestJson(method, path, options = {}) {
|
|
981
1751
|
const url = `${this.auth.apiBaseUrl.replace(/\/$/, "")}${path}${buildQuery(options.query)}`;
|
|
982
1752
|
const headers = {
|
|
983
1753
|
"content-type": "application/json",
|
|
984
|
-
"x-api-key": this.auth.apiKey
|
|
1754
|
+
"x-api-key": this.auth.apiKey,
|
|
1755
|
+
// Also sent as Authorization: Bearer — required for an OAuth access
|
|
1756
|
+
// token (the remote server's oauth_token sessions), which the auth
|
|
1757
|
+
// middleware only ever recognizes via this header (peeked by issuer),
|
|
1758
|
+
// never via x-api-key. For an sk_/pk_ key this is a no-op: the
|
|
1759
|
+
// middleware already treats `Authorization: Bearer sk_.../pk_...`
|
|
1760
|
+
// identically to `X-API-Key`, so existing stdio-CLI callers are
|
|
1761
|
+
// unaffected.
|
|
1762
|
+
Authorization: `Bearer ${this.auth.apiKey}`
|
|
985
1763
|
};
|
|
986
1764
|
if (options.idempotencyKey) headers["idempotency-key"] = options.idempotencyKey;
|
|
1765
|
+
if (options.applicationId && !API_KEY_PREFIX_RE.test(this.auth.apiKey)) {
|
|
1766
|
+
headers["x-throttle-application-id"] = options.applicationId;
|
|
1767
|
+
}
|
|
987
1768
|
const sendsBody = method === "POST" || method === "PATCH" || method === "PUT";
|
|
988
1769
|
const body = options.body !== void 0 ? JSON.stringify(options.body) : sendsBody ? "{}" : void 0;
|
|
989
1770
|
const res = await this.fetchImpl(url, { method, headers, body });
|
|
@@ -999,18 +1780,19 @@ var ThrottleClient = class {
|
|
|
999
1780
|
err?.code ?? `http_${res.status}`,
|
|
1000
1781
|
err?.message ?? `Request failed (${res.status}): ${text.slice(0, 200)}`,
|
|
1001
1782
|
res.status,
|
|
1002
|
-
err?.details
|
|
1783
|
+
err?.details,
|
|
1784
|
+
res.headers
|
|
1003
1785
|
);
|
|
1004
1786
|
}
|
|
1005
1787
|
return parsed;
|
|
1006
1788
|
}
|
|
1007
1789
|
/** Unwraps the `{ data }` envelope. Key presence, not `??` — `{data: null}` unwraps to null. */
|
|
1008
|
-
async get(path, query) {
|
|
1009
|
-
const body = await this.requestJson("GET", path, { query });
|
|
1790
|
+
async get(path, query, options) {
|
|
1791
|
+
const body = await this.requestJson("GET", path, { query, applicationId: options?.applicationId });
|
|
1010
1792
|
return body && typeof body === "object" && "data" in body ? body.data : body;
|
|
1011
1793
|
}
|
|
1012
|
-
async list(path, query) {
|
|
1013
|
-
const body = await this.requestJson("GET", path, { query });
|
|
1794
|
+
async list(path, query, options) {
|
|
1795
|
+
const body = await this.requestJson("GET", path, { query, applicationId: options?.applicationId });
|
|
1014
1796
|
const data = "data" in body ? body.data : body;
|
|
1015
1797
|
const meta = body.meta;
|
|
1016
1798
|
return { data: Array.isArray(data) ? data : [], pagination: meta?.pagination ?? null };
|
|
@@ -1019,6 +1801,10 @@ var ThrottleClient = class {
|
|
|
1019
1801
|
const body = await this.requestJson("POST", path, options);
|
|
1020
1802
|
return body && typeof body === "object" && "data" in body ? body.data : body;
|
|
1021
1803
|
}
|
|
1804
|
+
async patch(path, options = {}) {
|
|
1805
|
+
const body = await this.requestJson("PATCH", path, options);
|
|
1806
|
+
return body && typeof body === "object" && "data" in body ? body.data : body;
|
|
1807
|
+
}
|
|
1022
1808
|
};
|
|
1023
1809
|
async function optional(load) {
|
|
1024
1810
|
try {
|
|
@@ -1167,7 +1953,7 @@ function validatePayload(index, input) {
|
|
|
1167
1953
|
}
|
|
1168
1954
|
|
|
1169
1955
|
// src/docs.ts
|
|
1170
|
-
var DOCS_ORIGIN = "https://
|
|
1956
|
+
var DOCS_ORIGIN = "https://usethrottle.dev/docs";
|
|
1171
1957
|
function excerptAround(text, needle, radius = 160) {
|
|
1172
1958
|
const at = text.toLowerCase().indexOf(needle.toLowerCase());
|
|
1173
1959
|
if (at < 0) return text.slice(0, radius * 2).trim();
|
|
@@ -1251,27 +2037,35 @@ var HANDLERS = {
|
|
|
1251
2037
|
// ------------------------------------------------------------- generic read
|
|
1252
2038
|
api_get: async (ctx, args) => {
|
|
1253
2039
|
const path = String(args.path ?? "");
|
|
1254
|
-
|
|
2040
|
+
const resolved = new URL(path, "https://throttle.invalid").pathname;
|
|
2041
|
+
if (!path.startsWith("/api/v1/") || !resolved.startsWith("/api/v1/") || /(^|[\/\\])\.(\.)?([\/\\?#]|$)|%2e/i.test(path)) {
|
|
1255
2042
|
throw new Error(
|
|
1256
2043
|
`Refusing to call "${path}". api_get only reads paths beginning with /api/v1/ on the configured Throttle host.`
|
|
1257
2044
|
);
|
|
1258
2045
|
}
|
|
1259
|
-
return ctx.client.get(path);
|
|
2046
|
+
return ctx.client.get(path, void 0, { applicationId: args.applicationId });
|
|
1260
2047
|
},
|
|
1261
2048
|
// ------------------------------------------------------------------ orders
|
|
1262
|
-
search_orders: async (ctx, args) => ctx.client.list(
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
2049
|
+
search_orders: async (ctx, args) => ctx.client.list(
|
|
2050
|
+
"/api/v1/orders",
|
|
2051
|
+
{
|
|
2052
|
+
q: args.q,
|
|
2053
|
+
status: args.status,
|
|
2054
|
+
paymentStatus: args.paymentStatus,
|
|
2055
|
+
createdAfter: args.createdAfter,
|
|
2056
|
+
createdBefore: args.createdBefore,
|
|
2057
|
+
...page(args)
|
|
2058
|
+
},
|
|
2059
|
+
{ applicationId: args.applicationId }
|
|
2060
|
+
),
|
|
1268
2061
|
get_order: async (ctx, args) => {
|
|
1269
2062
|
const id = String(args.id);
|
|
1270
|
-
const
|
|
2063
|
+
const opts = { applicationId: args.applicationId };
|
|
2064
|
+
const order = await ctx.client.get(`/api/v1/orders/${id}`, void 0, opts);
|
|
1271
2065
|
const [payments, returns, fulfillments] = await Promise.all([
|
|
1272
|
-
optional(() => ctx.client.list(`/api/v1/orders/${id}/payments
|
|
1273
|
-
optional(() => ctx.client.list(`/api/v1/orders/${id}/returns
|
|
1274
|
-
optional(() => ctx.client.list(`/api/v1/orders/${id}/fulfillments
|
|
2066
|
+
optional(() => ctx.client.list(`/api/v1/orders/${id}/payments`, void 0, opts)),
|
|
2067
|
+
optional(() => ctx.client.list(`/api/v1/orders/${id}/returns`, void 0, opts)),
|
|
2068
|
+
optional(() => ctx.client.list(`/api/v1/orders/${id}/fulfillments`, void 0, opts))
|
|
1275
2069
|
]);
|
|
1276
2070
|
return {
|
|
1277
2071
|
order,
|
|
@@ -1281,13 +2075,23 @@ var HANDLERS = {
|
|
|
1281
2075
|
};
|
|
1282
2076
|
},
|
|
1283
2077
|
// --------------------------------------------------------------- customers
|
|
1284
|
-
search_customers: async (ctx, args) => ctx.client.list(
|
|
2078
|
+
search_customers: async (ctx, args) => ctx.client.list(
|
|
2079
|
+
"/api/v1/customers",
|
|
2080
|
+
{
|
|
2081
|
+
q: args.q,
|
|
2082
|
+
createdAfter: args.createdAfter,
|
|
2083
|
+
createdBefore: args.createdBefore,
|
|
2084
|
+
...page(args)
|
|
2085
|
+
},
|
|
2086
|
+
{ applicationId: args.applicationId }
|
|
2087
|
+
),
|
|
1285
2088
|
get_customer: async (ctx, args) => {
|
|
1286
2089
|
const id = String(args.id);
|
|
1287
|
-
const
|
|
2090
|
+
const opts = { applicationId: args.applicationId };
|
|
2091
|
+
const customer = await ctx.client.get(`/api/v1/customers/${id}`, void 0, opts);
|
|
1288
2092
|
const [addresses, subscriptions] = await Promise.all([
|
|
1289
|
-
optional(() => ctx.client.list(`/api/v1/customers/${id}/addresses
|
|
1290
|
-
optional(() => ctx.client.list("/api/v1/subscriptions", { customerId: id }))
|
|
2093
|
+
optional(() => ctx.client.list(`/api/v1/customers/${id}/addresses`, void 0, opts)),
|
|
2094
|
+
optional(() => ctx.client.list("/api/v1/subscriptions", { customerId: id }, opts))
|
|
1291
2095
|
]);
|
|
1292
2096
|
return {
|
|
1293
2097
|
customer,
|
|
@@ -1296,60 +2100,101 @@ var HANDLERS = {
|
|
|
1296
2100
|
};
|
|
1297
2101
|
},
|
|
1298
2102
|
// ----------------------------------------------------------- subscriptions
|
|
1299
|
-
list_subscriptions: async (ctx, args) => ctx.client.list(
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
2103
|
+
list_subscriptions: async (ctx, args) => ctx.client.list(
|
|
2104
|
+
"/api/v1/subscriptions",
|
|
2105
|
+
{
|
|
2106
|
+
customerId: args.customerId,
|
|
2107
|
+
status: args.status,
|
|
2108
|
+
createdAfter: args.createdAfter,
|
|
2109
|
+
createdBefore: args.createdBefore,
|
|
2110
|
+
...page(args)
|
|
2111
|
+
},
|
|
2112
|
+
{ applicationId: args.applicationId }
|
|
2113
|
+
),
|
|
2114
|
+
get_subscription: async (ctx, args) => ctx.client.get(`/api/v1/subscriptions/${String(args.id)}`, void 0, {
|
|
2115
|
+
applicationId: args.applicationId
|
|
1303
2116
|
}),
|
|
1304
|
-
get_subscription: async (ctx, args) => ctx.client.get(`/api/v1/subscriptions/${String(args.id)}`),
|
|
1305
2117
|
// ------------------------------------------------------------------ quotes
|
|
1306
|
-
search_quotes: async (ctx, args) => ctx.client.list(
|
|
1307
|
-
|
|
2118
|
+
search_quotes: async (ctx, args) => ctx.client.list(
|
|
2119
|
+
"/api/v1/quotes",
|
|
2120
|
+
{
|
|
2121
|
+
status: args.status,
|
|
2122
|
+
createdAfter: args.createdAfter,
|
|
2123
|
+
createdBefore: args.createdBefore,
|
|
2124
|
+
...page(args)
|
|
2125
|
+
},
|
|
2126
|
+
{ applicationId: args.applicationId }
|
|
2127
|
+
),
|
|
2128
|
+
get_quote: async (ctx, args) => ctx.client.get(`/api/v1/quotes/${String(args.id)}`, void 0, {
|
|
2129
|
+
applicationId: args.applicationId
|
|
2130
|
+
}),
|
|
1308
2131
|
// ---------------------------------------------------------------- invoices
|
|
1309
|
-
list_invoices: async (ctx, args) => ctx.client.list(
|
|
1310
|
-
|
|
2132
|
+
list_invoices: async (ctx, args) => ctx.client.list(
|
|
2133
|
+
"/api/v1/invoices",
|
|
2134
|
+
{ status: args.status, ...page(args) },
|
|
2135
|
+
{ applicationId: args.applicationId }
|
|
2136
|
+
),
|
|
2137
|
+
receivables_aging: async (ctx, args) => ctx.client.get("/api/v1/invoices/aging", void 0, { applicationId: args.applicationId }),
|
|
1311
2138
|
// --------------------------------------------------------------- discounts
|
|
1312
|
-
search_discounts: async (ctx, args) => ctx.client.list(
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
2139
|
+
search_discounts: async (ctx, args) => ctx.client.list(
|
|
2140
|
+
"/api/v1/discounts",
|
|
2141
|
+
{
|
|
2142
|
+
q: args.q,
|
|
2143
|
+
code: args.code,
|
|
2144
|
+
type: args.type,
|
|
2145
|
+
isActive: args.isActive,
|
|
2146
|
+
createdAfter: args.createdAfter,
|
|
2147
|
+
createdBefore: args.createdBefore,
|
|
2148
|
+
...page(args)
|
|
2149
|
+
},
|
|
2150
|
+
{ applicationId: args.applicationId }
|
|
2151
|
+
),
|
|
2152
|
+
get_discount: async (ctx, args) => ctx.client.get(`/api/v1/discounts/${String(args.id)}`, void 0, {
|
|
2153
|
+
applicationId: args.applicationId
|
|
1320
2154
|
}),
|
|
1321
|
-
get_discount: async (ctx, args) => ctx.client.get(`/api/v1/discounts/${String(args.id)}`),
|
|
1322
2155
|
// ---------------------------------------------------------------- payments
|
|
1323
|
-
search_payments: async (ctx, args) => ctx.client.list(
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
2156
|
+
search_payments: async (ctx, args) => ctx.client.list(
|
|
2157
|
+
"/api/v1/payments",
|
|
2158
|
+
{
|
|
2159
|
+
method: args.method,
|
|
2160
|
+
status: args.status,
|
|
2161
|
+
// The route only accepts these as snake_case query params -- confirmed
|
|
2162
|
+
// by reading its querystring schema directly, not assumed from the
|
|
2163
|
+
// platform's usual camelCase convention.
|
|
2164
|
+
order_id: args.orderId,
|
|
2165
|
+
aging: args.aging,
|
|
2166
|
+
due_from: args.dueFrom,
|
|
2167
|
+
due_to: args.dueTo,
|
|
2168
|
+
amount_min: args.amountMin,
|
|
2169
|
+
amount_max: args.amountMax,
|
|
2170
|
+
...page(args)
|
|
2171
|
+
},
|
|
2172
|
+
{ applicationId: args.applicationId }
|
|
2173
|
+
),
|
|
1337
2174
|
get_payment: async (ctx, args) => {
|
|
1338
2175
|
const id = String(args.id);
|
|
1339
|
-
const
|
|
1340
|
-
const
|
|
2176
|
+
const opts = { applicationId: args.applicationId };
|
|
2177
|
+
const payment = await ctx.client.get(`/api/v1/payments/${id}`, void 0, opts);
|
|
2178
|
+
const transactions = await optional(
|
|
2179
|
+
() => ctx.client.list(`/api/v1/payments/${id}/transactions`, void 0, opts)
|
|
2180
|
+
);
|
|
1341
2181
|
return {
|
|
1342
2182
|
payment,
|
|
1343
2183
|
transactions: transactions.ok ? transactions.value.data : transactions.unavailable
|
|
1344
2184
|
};
|
|
1345
2185
|
},
|
|
1346
2186
|
// ---------------------------------------------------------------- invoices
|
|
1347
|
-
get_invoice: async (ctx, args) => ctx.client.get(`/api/v1/invoices/${String(args.id)}
|
|
2187
|
+
get_invoice: async (ctx, args) => ctx.client.get(`/api/v1/invoices/${String(args.id)}`, void 0, {
|
|
2188
|
+
applicationId: args.applicationId
|
|
2189
|
+
}),
|
|
1348
2190
|
// ------------------------------------------------------------------ carts
|
|
1349
2191
|
list_abandoned_carts: async (ctx, args) => {
|
|
2192
|
+
const opts = { applicationId: args.applicationId };
|
|
1350
2193
|
const [carts, summary] = await Promise.all([
|
|
1351
|
-
optional(() => ctx.client.list("/api/v1/abandoned-carts", { ...page(args) })),
|
|
1352
|
-
optional(
|
|
2194
|
+
optional(() => ctx.client.list("/api/v1/abandoned-carts", { ...page(args) }, opts)),
|
|
2195
|
+
optional(
|
|
2196
|
+
() => ctx.client.get("/api/v1/abandoned-carts/summary", { sinceDays: args.sinceDays }, opts)
|
|
2197
|
+
)
|
|
1353
2198
|
]);
|
|
1354
2199
|
return {
|
|
1355
2200
|
carts: carts.ok ? carts.value.data : carts.unavailable,
|
|
@@ -1357,18 +2202,29 @@ var HANDLERS = {
|
|
|
1357
2202
|
};
|
|
1358
2203
|
},
|
|
1359
2204
|
// --------------------------------------------------------------- debugging
|
|
1360
|
-
search_events: async (ctx, args) => ctx.client.list(
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
2205
|
+
search_events: async (ctx, args) => ctx.client.list(
|
|
2206
|
+
"/api/v1/events",
|
|
2207
|
+
{
|
|
2208
|
+
type: args.type,
|
|
2209
|
+
entityType: args.entityType,
|
|
2210
|
+
entityId: args.entityId,
|
|
2211
|
+
...page(args)
|
|
2212
|
+
},
|
|
2213
|
+
{ applicationId: args.applicationId }
|
|
2214
|
+
),
|
|
2215
|
+
list_webhook_deliveries: async (ctx, args) => ctx.client.list(
|
|
2216
|
+
"/api/v1/webhook-deliveries",
|
|
2217
|
+
{
|
|
2218
|
+
endpointId: args.endpointId,
|
|
2219
|
+
status: args.status,
|
|
2220
|
+
eventType: args.eventType,
|
|
2221
|
+
limit: args.limit
|
|
2222
|
+
},
|
|
2223
|
+
{ applicationId: args.applicationId }
|
|
2224
|
+
),
|
|
2225
|
+
get_webhook_delivery: async (ctx, args) => ctx.client.get(`/api/v1/webhook-deliveries/${String(args.id)}`, void 0, {
|
|
2226
|
+
applicationId: args.applicationId
|
|
1370
2227
|
}),
|
|
1371
|
-
get_webhook_delivery: async (ctx, args) => ctx.client.get(`/api/v1/webhook-deliveries/${String(args.id)}`),
|
|
1372
2228
|
send_test_webhook: async (ctx, args) => ctx.client.post(`/api/v1/webhook-endpoints/${String(args.id)}/test`, {
|
|
1373
2229
|
body: { eventType: args.eventType },
|
|
1374
2230
|
idempotencyKey: `mcp-send-test-webhook-${String(args.id)}-${Date.now()}`
|
|
@@ -1378,18 +2234,21 @@ var HANDLERS = {
|
|
|
1378
2234
|
}),
|
|
1379
2235
|
explain_checkout_failure: async (ctx, args) => {
|
|
1380
2236
|
const id = String(args.id);
|
|
1381
|
-
const
|
|
2237
|
+
const opts = { applicationId: args.applicationId };
|
|
2238
|
+
const session = await optional(
|
|
2239
|
+
() => ctx.client.get(`/api/v1/checkout-sessions/${id}`, void 0, opts)
|
|
2240
|
+
);
|
|
1382
2241
|
if (!session.ok) {
|
|
1383
2242
|
return {
|
|
1384
|
-
note: `No checkout session ${id} (${session.unavailable}). If this is a cart id,
|
|
2243
|
+
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).`
|
|
1385
2244
|
};
|
|
1386
2245
|
}
|
|
1387
2246
|
const cartId = session.value?.cartId ?? void 0;
|
|
1388
2247
|
const [cart, sessionEvents, cartEvents, deliveries] = await Promise.all([
|
|
1389
|
-
optional(() => ctx.client.get(`/api/v1/checkout-sessions/${id}/cart
|
|
1390
|
-
optional(() => ctx.client.list("/api/v1/events", { entityId: id, limit: 25 })),
|
|
1391
|
-
cartId ? optional(() => ctx.client.list("/api/v1/events", { entityId: cartId, limit: 25 })) : Promise.resolve({ ok: true, value: { data: [], pagination: null } }),
|
|
1392
|
-
optional(() => ctx.client.list("/api/v1/webhook-deliveries", { limit: 15 }))
|
|
2248
|
+
optional(() => ctx.client.get(`/api/v1/checkout-sessions/${id}/cart`, void 0, opts)),
|
|
2249
|
+
optional(() => ctx.client.list("/api/v1/events", { entityId: id, limit: 25 }, opts)),
|
|
2250
|
+
cartId ? optional(() => ctx.client.list("/api/v1/events", { entityId: cartId, limit: 25 }, opts)) : Promise.resolve({ ok: true, value: { data: [], pagination: null } }),
|
|
2251
|
+
optional(() => ctx.client.list("/api/v1/webhook-deliveries", { limit: 15 }, opts))
|
|
1393
2252
|
]);
|
|
1394
2253
|
return {
|
|
1395
2254
|
session: session.value,
|
|
@@ -1449,6 +2308,73 @@ var HANDLERS = {
|
|
|
1449
2308
|
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."
|
|
1450
2309
|
};
|
|
1451
2310
|
},
|
|
2311
|
+
// ---------------------------------------------------------- inbound writes
|
|
2312
|
+
create_customer: async (ctx, args) => ctx.client.post("/api/v1/customers", {
|
|
2313
|
+
// `createCustomerBody` is `.strict()`, so only fields it declares may be
|
|
2314
|
+
// sent; `undefined` values drop out at JSON.stringify.
|
|
2315
|
+
body: {
|
|
2316
|
+
email: args.email,
|
|
2317
|
+
firstName: args.firstName,
|
|
2318
|
+
lastName: args.lastName,
|
|
2319
|
+
phone: args.phone,
|
|
2320
|
+
company: args.company,
|
|
2321
|
+
externalId: args.externalId,
|
|
2322
|
+
netN: args.netN,
|
|
2323
|
+
acceptsMarketing: args.acceptsMarketing
|
|
2324
|
+
},
|
|
2325
|
+
idempotencyKey: `mcp-create-customer-${String(args.email)}-${Date.now()}`
|
|
2326
|
+
}),
|
|
2327
|
+
update_customer: async (ctx, args) => ctx.client.patch(`/api/v1/customers/${String(args.id)}`, {
|
|
2328
|
+
// `id` is the path parameter, not a body field — `updateCustomerBody` is
|
|
2329
|
+
// `.strict()` and would reject it. A null `externalId` is meaningful
|
|
2330
|
+
// (it clears the value), so only `undefined` is dropped.
|
|
2331
|
+
body: {
|
|
2332
|
+
email: args.email,
|
|
2333
|
+
firstName: args.firstName,
|
|
2334
|
+
lastName: args.lastName,
|
|
2335
|
+
phone: args.phone,
|
|
2336
|
+
company: args.company,
|
|
2337
|
+
externalId: args.externalId,
|
|
2338
|
+
netN: args.netN,
|
|
2339
|
+
acceptsMarketing: args.acceptsMarketing
|
|
2340
|
+
},
|
|
2341
|
+
idempotencyKey: `mcp-update-customer-${String(args.id)}-${Date.now()}`
|
|
2342
|
+
}),
|
|
2343
|
+
create_checkout_link: async (ctx, args) => {
|
|
2344
|
+
const lineItems = Array.isArray(args.lineItems) ? args.lineItems : [];
|
|
2345
|
+
if (lineItems.length === 0) {
|
|
2346
|
+
throw new Error("create_checkout_link: lineItems must contain at least one item.");
|
|
2347
|
+
}
|
|
2348
|
+
const stamp = Date.now();
|
|
2349
|
+
const applicationId = ctx.identity.application?.id;
|
|
2350
|
+
const cart = await ctx.client.post("/api/v1/carts", {
|
|
2351
|
+
body: {
|
|
2352
|
+
applicationId,
|
|
2353
|
+
customerId: args.customerId,
|
|
2354
|
+
customerEmail: args.customerEmail,
|
|
2355
|
+
netN: args.netN
|
|
2356
|
+
},
|
|
2357
|
+
idempotencyKey: `mcp-checkout-link-cart-${stamp}`
|
|
2358
|
+
});
|
|
2359
|
+
for (const [index, item] of lineItems.entries()) {
|
|
2360
|
+
await ctx.client.post(`/api/v1/carts/${cart?.id}/items`, {
|
|
2361
|
+
body: item,
|
|
2362
|
+
idempotencyKey: `mcp-checkout-link-item-${stamp}-${index}`
|
|
2363
|
+
});
|
|
2364
|
+
}
|
|
2365
|
+
const session = await ctx.client.post("/api/v1/checkout/sessions", {
|
|
2366
|
+
body: {
|
|
2367
|
+
applicationId,
|
|
2368
|
+
cartId: cart?.id,
|
|
2369
|
+
returnUrl: args.returnUrl,
|
|
2370
|
+
cancelUrl: args.cancelUrl,
|
|
2371
|
+
customerEmail: args.customerEmail,
|
|
2372
|
+
discountCode: args.discountCode
|
|
2373
|
+
},
|
|
2374
|
+
idempotencyKey: `mcp-checkout-link-session-${stamp}`
|
|
2375
|
+
});
|
|
2376
|
+
return { cartId: cart?.id, ...session };
|
|
2377
|
+
},
|
|
1452
2378
|
// -------------------------------------------------------------- money/mail
|
|
1453
2379
|
manage_subscription: async (ctx, args) => {
|
|
1454
2380
|
const id = String(args.id);
|
|
@@ -1478,17 +2404,118 @@ var HANDLERS = {
|
|
|
1478
2404
|
idempotencyKey: `mcp-create-quote-${String(args.customerId)}-${Date.now()}`
|
|
1479
2405
|
})
|
|
1480
2406
|
),
|
|
1481
|
-
|
|
1482
|
-
|
|
2407
|
+
get_sales_timeseries: async (ctx, args) => ctx.client.get(
|
|
2408
|
+
"/api/v1/analytics/orders-timeseries",
|
|
2409
|
+
{
|
|
2410
|
+
from: args.from,
|
|
2411
|
+
to: args.to,
|
|
2412
|
+
groupBy: args.groupBy,
|
|
2413
|
+
timezone: args.timezone,
|
|
2414
|
+
compareTo: args.compareTo
|
|
2415
|
+
},
|
|
2416
|
+
{ applicationId: args.applicationId }
|
|
2417
|
+
),
|
|
2418
|
+
get_business_summary: async (ctx, args) => ctx.client.get(
|
|
2419
|
+
"/api/v1/analytics/business-summary",
|
|
2420
|
+
{
|
|
2421
|
+
period: args.period,
|
|
2422
|
+
from: args.from,
|
|
2423
|
+
to: args.to,
|
|
2424
|
+
timezone: args.timezone
|
|
2425
|
+
},
|
|
2426
|
+
{ applicationId: args.applicationId }
|
|
2427
|
+
),
|
|
2428
|
+
get_attention_digest: async (ctx, args) => ctx.client.get("/api/v1/analytics/attention-digest", void 0, {
|
|
2429
|
+
applicationId: args.applicationId
|
|
2430
|
+
}),
|
|
2431
|
+
get_top_products: async (ctx, args) => ctx.client.get(
|
|
2432
|
+
"/api/v1/analytics/products",
|
|
2433
|
+
{
|
|
2434
|
+
from: args.from,
|
|
2435
|
+
to: args.to,
|
|
2436
|
+
timezone: args.timezone,
|
|
2437
|
+
by: args.by,
|
|
2438
|
+
limit: args.limit
|
|
2439
|
+
},
|
|
2440
|
+
{ applicationId: args.applicationId }
|
|
2441
|
+
),
|
|
2442
|
+
// `.get()`, not `.list()` — the response is a single object (`{ data: {
|
|
2443
|
+
// range, signups, ordersByCustomerType, topCustomers, totals } }`), and
|
|
2444
|
+
// `.list()` only unwraps arrays: it would silently return an empty list
|
|
2445
|
+
// instead of the real payload.
|
|
2446
|
+
get_customer_growth: async (ctx, args) => ctx.client.get(
|
|
2447
|
+
"/api/v1/analytics/customers",
|
|
2448
|
+
{
|
|
2449
|
+
from: args.from,
|
|
2450
|
+
to: args.to,
|
|
2451
|
+
groupBy: args.groupBy,
|
|
2452
|
+
timezone: args.timezone,
|
|
2453
|
+
topBy: args.topBy,
|
|
2454
|
+
topLimit: args.topLimit
|
|
2455
|
+
},
|
|
2456
|
+
{ applicationId: args.applicationId }
|
|
2457
|
+
),
|
|
2458
|
+
// `.get()`, not `.list()` — the response is a single object (`{ data: {
|
|
2459
|
+
// range, dimension, rows, totals, note } }`), not a paginated array.
|
|
2460
|
+
get_revenue_breakdown: async (ctx, args) => ctx.client.get(
|
|
2461
|
+
"/api/v1/analytics/revenue-breakdown",
|
|
2462
|
+
{
|
|
2463
|
+
dimension: args.dimension,
|
|
2464
|
+
from: args.from,
|
|
2465
|
+
to: args.to,
|
|
2466
|
+
timezone: args.timezone
|
|
2467
|
+
},
|
|
2468
|
+
{ applicationId: args.applicationId }
|
|
2469
|
+
),
|
|
2470
|
+
// `.get()`, not `.list()` — the response is a single object (`{ data: {
|
|
2471
|
+
// range, stages, conversionRate, dropOff, note } }`), not a paginated array.
|
|
2472
|
+
get_funnel_conversion: async (ctx, args) => ctx.client.get(
|
|
2473
|
+
"/api/v1/analytics/funnel",
|
|
2474
|
+
{
|
|
2475
|
+
from: args.from,
|
|
2476
|
+
to: args.to,
|
|
2477
|
+
timezone: args.timezone
|
|
2478
|
+
},
|
|
2479
|
+
{ applicationId: args.applicationId }
|
|
2480
|
+
),
|
|
2481
|
+
// `.get()`, not `.list()` — the response is a single object (`{ data: {
|
|
2482
|
+
// range, series, totals, note } }`), not a paginated array.
|
|
2483
|
+
get_subscription_movement: async (ctx, args) => ctx.client.get(
|
|
2484
|
+
"/api/v1/analytics/subscriptions",
|
|
2485
|
+
{
|
|
2486
|
+
from: args.from,
|
|
2487
|
+
to: args.to,
|
|
2488
|
+
timezone: args.timezone,
|
|
2489
|
+
groupBy: args.groupBy
|
|
2490
|
+
},
|
|
2491
|
+
{ applicationId: args.applicationId }
|
|
2492
|
+
),
|
|
2493
|
+
// `.get()`, not `.list()` — the response is a single object (`{ data: {
|
|
2494
|
+
// range, rows, totals, note } }`), not a paginated array.
|
|
2495
|
+
get_discount_performance: async (ctx, args) => ctx.client.get(
|
|
2496
|
+
"/api/v1/analytics/discounts",
|
|
2497
|
+
{
|
|
2498
|
+
from: args.from,
|
|
2499
|
+
to: args.to,
|
|
2500
|
+
timezone: args.timezone,
|
|
2501
|
+
limit: args.limit
|
|
2502
|
+
},
|
|
2503
|
+
{ applicationId: args.applicationId }
|
|
2504
|
+
),
|
|
1483
2505
|
// ------------------------------------------------------------- extensions
|
|
1484
|
-
list_extension_installations: async (ctx, args) => ctx.client.list(
|
|
2506
|
+
list_extension_installations: async (ctx, args) => ctx.client.list(
|
|
2507
|
+
"/api/v1/installations",
|
|
2508
|
+
{ status: args.status, ...page(args) },
|
|
2509
|
+
{ applicationId: args.applicationId }
|
|
2510
|
+
),
|
|
1485
2511
|
// ------------------------------------------------------------- connectors
|
|
1486
|
-
get_connector_routing: async (ctx) => {
|
|
2512
|
+
get_connector_routing: async (ctx, args) => {
|
|
2513
|
+
const opts = { applicationId: args.applicationId };
|
|
1487
2514
|
const [paymentRouting, routingConnectors, cardDecline, otherDecline] = await Promise.all([
|
|
1488
|
-
optional(() => ctx.client.get("/api/v1/payment-routing")),
|
|
1489
|
-
optional(() => ctx.client.get("/api/v1/routing-connectors")),
|
|
1490
|
-
optional(() => ctx.client.get("/api/v1/card-decline")),
|
|
1491
|
-
optional(() => ctx.client.get("/api/v1/other-decline"))
|
|
2515
|
+
optional(() => ctx.client.get("/api/v1/payment-routing", void 0, opts)),
|
|
2516
|
+
optional(() => ctx.client.get("/api/v1/routing-connectors", void 0, opts)),
|
|
2517
|
+
optional(() => ctx.client.get("/api/v1/card-decline", void 0, opts)),
|
|
2518
|
+
optional(() => ctx.client.get("/api/v1/other-decline", void 0, opts))
|
|
1492
2519
|
]);
|
|
1493
2520
|
return {
|
|
1494
2521
|
paymentRouting: paymentRouting.ok ? paymentRouting.value : paymentRouting.unavailable,
|
|
@@ -1497,24 +2524,37 @@ var HANDLERS = {
|
|
|
1497
2524
|
otherDecline: otherDecline.ok ? otherDecline.value : otherDecline.unavailable
|
|
1498
2525
|
};
|
|
1499
2526
|
},
|
|
1500
|
-
get_integration_status: async (ctx) => {
|
|
2527
|
+
get_integration_status: async (ctx, args) => {
|
|
2528
|
+
const opts = { applicationId: args.applicationId };
|
|
1501
2529
|
const [connectors, endpoints, coverage] = await Promise.all([
|
|
1502
|
-
optional(() => ctx.client.list("/api/v1/connectors")),
|
|
1503
|
-
optional(() => ctx.client.list("/api/v1/webhook-endpoints")),
|
|
1504
|
-
optional(() => ctx.client.get("/api/v1/webhook-endpoints/coverage"))
|
|
2530
|
+
optional(() => ctx.client.list("/api/v1/connectors", void 0, opts)),
|
|
2531
|
+
optional(() => ctx.client.list("/api/v1/webhook-endpoints", void 0, opts)),
|
|
2532
|
+
optional(() => ctx.client.get("/api/v1/webhook-endpoints/coverage", void 0, opts))
|
|
1505
2533
|
]);
|
|
2534
|
+
const overridden = Boolean(args.applicationId) && args.applicationId !== ctx.identity.application?.id;
|
|
1506
2535
|
return {
|
|
1507
2536
|
environment: ctx.identity.environment,
|
|
1508
2537
|
workspace: ctx.identity.workspace,
|
|
1509
|
-
|
|
2538
|
+
applicationId: args.applicationId ?? ctx.identity.application?.id ?? null,
|
|
2539
|
+
application: overridden ? void 0 : ctx.identity.application,
|
|
1510
2540
|
connectors: connectors.ok ? connectors.value.data : connectors.unavailable,
|
|
1511
2541
|
webhookEndpoints: endpoints.ok ? endpoints.value.data : endpoints.unavailable,
|
|
1512
2542
|
unsubscribedEvents: coverage.ok ? coverage.value : coverage.unavailable
|
|
1513
2543
|
};
|
|
1514
2544
|
},
|
|
1515
2545
|
// --------------------------------------------------------------- workspace
|
|
2546
|
+
// No applicationId forwarding here: /api/v1/merchants/me is workspace-only
|
|
2547
|
+
// (`WHERE workspace_id = ...`, never reads auth.applicationId) — an
|
|
2548
|
+
// applicationId field on this tool would be a silent no-op affordance.
|
|
1516
2549
|
get_workspace_settings: async (ctx) => ctx.client.get("/api/v1/merchants/me"),
|
|
1517
|
-
|
|
2550
|
+
// No applicationId forwarding here either: /api/v1/audit-log filters by
|
|
2551
|
+
// workspaceId (and optionally environmentId), never applicationId.
|
|
2552
|
+
get_audit_log: async (ctx, args) => ctx.client.list("/api/v1/audit-log", { limit: args.limit }),
|
|
2553
|
+
// --------------------------------------------------------- applications
|
|
2554
|
+
// `.get()`, not `.list()` — the legacy /v1/stores route returns a bare
|
|
2555
|
+
// array with no `meta.pagination`, and the brief calls for `.get()` here
|
|
2556
|
+
// specifically.
|
|
2557
|
+
list_applications: async (ctx) => ctx.client.get("/api/v1/stores")
|
|
1518
2558
|
};
|
|
1519
2559
|
|
|
1520
2560
|
// src/input.ts
|
|
@@ -1549,15 +2589,548 @@ function applyModifiers(field, type) {
|
|
|
1549
2589
|
return field.default === void 0 ? described.optional() : described.default(field.default);
|
|
1550
2590
|
}
|
|
1551
2591
|
|
|
2592
|
+
// src/projection.ts
|
|
2593
|
+
var S = "scalar";
|
|
2594
|
+
var T = "text";
|
|
2595
|
+
var PASS = "pass";
|
|
2596
|
+
var section = (shape) => ({ __section: shape });
|
|
2597
|
+
var oneOf = (values) => ({ __oneOf: new Set(values) });
|
|
2598
|
+
function isPrimitive(value) {
|
|
2599
|
+
return value === null || ["string", "number", "boolean"].includes(typeof value);
|
|
2600
|
+
}
|
|
2601
|
+
var TOKEN = /^[^\s@]{0,200}$/;
|
|
2602
|
+
var OMITTED = { omitted: S, grantable: T, reason: T };
|
|
2603
|
+
var UNAVAILABLE = /^(not readable with this credential \(missing scope\)|not found|unavailable \([^\s@]{0,100}\))$/;
|
|
2604
|
+
function project(value, shape) {
|
|
2605
|
+
if (value === void 0) return void 0;
|
|
2606
|
+
if (shape === PASS) return value;
|
|
2607
|
+
if (shape === S) {
|
|
2608
|
+
if (!isPrimitive(value)) return void 0;
|
|
2609
|
+
return typeof value === "string" && !TOKEN.test(value) ? null : value;
|
|
2610
|
+
}
|
|
2611
|
+
if (shape === T) return isPrimitive(value) ? value : void 0;
|
|
2612
|
+
if ("__oneOf" in shape) {
|
|
2613
|
+
if (!isPrimitive(value)) return void 0;
|
|
2614
|
+
return typeof value === "string" && !shape.__oneOf.has(value) ? null : value;
|
|
2615
|
+
}
|
|
2616
|
+
if (Array.isArray(shape)) {
|
|
2617
|
+
return Array.isArray(value) ? value.map((item) => project(item, shape[0])) : void 0;
|
|
2618
|
+
}
|
|
2619
|
+
if ("__section" in shape) {
|
|
2620
|
+
if (typeof value === "string") return UNAVAILABLE.test(value) ? value : null;
|
|
2621
|
+
if (value && typeof value === "object" && value.omitted === true) {
|
|
2622
|
+
return project(value, OMITTED);
|
|
2623
|
+
}
|
|
2624
|
+
return project(value, shape.__section);
|
|
2625
|
+
}
|
|
2626
|
+
if (value === null) return null;
|
|
2627
|
+
if (typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
2628
|
+
const out = {};
|
|
2629
|
+
for (const [key, child] of Object.entries(shape)) {
|
|
2630
|
+
const projected = project(value[key], child);
|
|
2631
|
+
if (projected !== void 0) out[key] = projected;
|
|
2632
|
+
}
|
|
2633
|
+
return out;
|
|
2634
|
+
}
|
|
2635
|
+
var PAGINATION = { cursor: S, nextCursor: S, hasMore: S };
|
|
2636
|
+
var page2 = (row) => ({ data: [row], pagination: PAGINATION });
|
|
2637
|
+
var ADDRESS = { countryCode: S };
|
|
2638
|
+
var CUSTOMER_REF = { id: S };
|
|
2639
|
+
var APPLICATION = { id: S, name: T, slug: S };
|
|
2640
|
+
var LIFECYCLE_LABELS = ["Draft", "Pending", "Processing", "Partially fulfilled", "Fulfilled", "Cancelled", "Closed"];
|
|
2641
|
+
var DISPLAY_QUALIFIERS = [
|
|
2642
|
+
"Comped",
|
|
2643
|
+
"Awaiting payment",
|
|
2644
|
+
"Authorized",
|
|
2645
|
+
"Partially paid",
|
|
2646
|
+
"Partially refunded",
|
|
2647
|
+
"Refunded",
|
|
2648
|
+
"Payment failed",
|
|
2649
|
+
"Voided",
|
|
2650
|
+
"Payment processing",
|
|
2651
|
+
"Disputed",
|
|
2652
|
+
"Authorization expired",
|
|
2653
|
+
"Returned",
|
|
2654
|
+
"Partially returned",
|
|
2655
|
+
"Delivered",
|
|
2656
|
+
"In transit"
|
|
2657
|
+
];
|
|
2658
|
+
var ORDER_DISPLAY_STATUSES = new Set(
|
|
2659
|
+
LIFECYCLE_LABELS.flatMap((l) => [l, ...DISPLAY_QUALIFIERS.map((q) => `${l} \xB7 ${q}`)])
|
|
2660
|
+
);
|
|
2661
|
+
var DISPLAY_STATUS = oneOf(ORDER_DISPLAY_STATUSES);
|
|
2662
|
+
var RANGE = { from: S, to: S, timezone: S, groupBy: S };
|
|
2663
|
+
var LINE_ITEM = {
|
|
2664
|
+
id: S,
|
|
2665
|
+
type: S,
|
|
2666
|
+
quantity: S,
|
|
2667
|
+
unitPrice: S,
|
|
2668
|
+
subtotal: S,
|
|
2669
|
+
discountAmount: S,
|
|
2670
|
+
taxAmount: S,
|
|
2671
|
+
total: S,
|
|
2672
|
+
recurring: S
|
|
2673
|
+
};
|
|
2674
|
+
var ORDER = {
|
|
2675
|
+
id: S,
|
|
2676
|
+
orderNumber: S,
|
|
2677
|
+
status: S,
|
|
2678
|
+
displayStatus: DISPLAY_STATUS,
|
|
2679
|
+
paymentStatus: S,
|
|
2680
|
+
type: S,
|
|
2681
|
+
source: S,
|
|
2682
|
+
currency: S,
|
|
2683
|
+
subtotal: S,
|
|
2684
|
+
discountTotal: S,
|
|
2685
|
+
shippingTotal: S,
|
|
2686
|
+
taxTotal: S,
|
|
2687
|
+
total: S,
|
|
2688
|
+
capturedAmount: S,
|
|
2689
|
+
refundedAmount: S,
|
|
2690
|
+
compAmount: S,
|
|
2691
|
+
onHold: S,
|
|
2692
|
+
taxReverseCharge: S,
|
|
2693
|
+
customerId: S,
|
|
2694
|
+
customer: CUSTOMER_REF,
|
|
2695
|
+
subscriptionId: S,
|
|
2696
|
+
shippingAddress: ADDRESS,
|
|
2697
|
+
billingAddress: ADDRESS,
|
|
2698
|
+
fulfillmentSummary: { itemsFulfilled: S, itemsRemaining: S, itemsTotal: S },
|
|
2699
|
+
lineItems: [LINE_ITEM],
|
|
2700
|
+
createdAt: S,
|
|
2701
|
+
completedAt: S,
|
|
2702
|
+
cancelledAt: S,
|
|
2703
|
+
closedAt: S
|
|
2704
|
+
};
|
|
2705
|
+
var PAYMENT = {
|
|
2706
|
+
id: S,
|
|
2707
|
+
orderId: S,
|
|
2708
|
+
status: S,
|
|
2709
|
+
method: S,
|
|
2710
|
+
processor: S,
|
|
2711
|
+
amount: S,
|
|
2712
|
+
capturedAmount: S,
|
|
2713
|
+
currency: S,
|
|
2714
|
+
cardBrand: S,
|
|
2715
|
+
disputed: S,
|
|
2716
|
+
disputeOpenedAt: S,
|
|
2717
|
+
errorCode: S,
|
|
2718
|
+
netN: S,
|
|
2719
|
+
netNSource: S,
|
|
2720
|
+
authorizedAt: S,
|
|
2721
|
+
capturedAt: S,
|
|
2722
|
+
voidedAt: S,
|
|
2723
|
+
createdAt: S
|
|
2724
|
+
};
|
|
2725
|
+
var RETURN = {
|
|
2726
|
+
id: S,
|
|
2727
|
+
orderId: S,
|
|
2728
|
+
status: S,
|
|
2729
|
+
restock: S,
|
|
2730
|
+
refundAmount: S,
|
|
2731
|
+
refundPaymentId: S,
|
|
2732
|
+
createdAt: S,
|
|
2733
|
+
items: [{ lineItemId: S, quantity: S }]
|
|
2734
|
+
};
|
|
2735
|
+
var FULFILLMENT = {
|
|
2736
|
+
id: S,
|
|
2737
|
+
orderId: S,
|
|
2738
|
+
type: S,
|
|
2739
|
+
status: S,
|
|
2740
|
+
createdAt: S,
|
|
2741
|
+
lineItems: [{ lineItemId: S, quantity: S }],
|
|
2742
|
+
detail: { carrier: T, serviceLevel: T, trackingNumber: T, trackingUrl: T, shippedAt: S, estimatedDelivery: S, actualDelivery: S }
|
|
2743
|
+
};
|
|
2744
|
+
var CUSTOMER = { id: S, netN: S, acceptsMarketing: S, createdAt: S };
|
|
2745
|
+
var SUBSCRIPTION = {
|
|
2746
|
+
id: S,
|
|
2747
|
+
status: S,
|
|
2748
|
+
planName: T,
|
|
2749
|
+
planReference: T,
|
|
2750
|
+
amount: S,
|
|
2751
|
+
currency: S,
|
|
2752
|
+
interval: S,
|
|
2753
|
+
quantity: S,
|
|
2754
|
+
intervalUnit: S,
|
|
2755
|
+
intervalCount: S,
|
|
2756
|
+
totalPayments: S,
|
|
2757
|
+
paymentsMade: S,
|
|
2758
|
+
completedAt: S,
|
|
2759
|
+
customerId: S,
|
|
2760
|
+
customer: CUSTOMER_REF,
|
|
2761
|
+
currentPeriodStart: S,
|
|
2762
|
+
currentPeriodEnd: S,
|
|
2763
|
+
billingAnchorAt: S,
|
|
2764
|
+
cancelAtPeriodEnd: S,
|
|
2765
|
+
trialEnd: S,
|
|
2766
|
+
pausedAt: S,
|
|
2767
|
+
cancelledAt: S,
|
|
2768
|
+
lastPaymentAt: S,
|
|
2769
|
+
failureCount: S,
|
|
2770
|
+
pendingPlanName: T,
|
|
2771
|
+
pendingPlanReference: T,
|
|
2772
|
+
pendingAmount: S,
|
|
2773
|
+
pendingInterval: S,
|
|
2774
|
+
pendingQuantity: S,
|
|
2775
|
+
pendingIntervalUnit: S,
|
|
2776
|
+
pendingIntervalCount: S,
|
|
2777
|
+
createdAt: S
|
|
2778
|
+
};
|
|
2779
|
+
var QUOTE_SUMMARY = {
|
|
2780
|
+
id: S,
|
|
2781
|
+
quoteNumber: S,
|
|
2782
|
+
status: S,
|
|
2783
|
+
source: S,
|
|
2784
|
+
currency: S,
|
|
2785
|
+
total: S,
|
|
2786
|
+
customerId: S,
|
|
2787
|
+
orderId: S,
|
|
2788
|
+
expiresAt: S,
|
|
2789
|
+
acceptedAt: S,
|
|
2790
|
+
declinedAt: S,
|
|
2791
|
+
createdAt: S
|
|
2792
|
+
};
|
|
2793
|
+
var QUOTE = {
|
|
2794
|
+
...QUOTE_SUMMARY,
|
|
2795
|
+
revisions: [{
|
|
2796
|
+
id: S,
|
|
2797
|
+
revisionNumber: S,
|
|
2798
|
+
state: S,
|
|
2799
|
+
subtotal: S,
|
|
2800
|
+
discountTotal: S,
|
|
2801
|
+
shippingTotal: S,
|
|
2802
|
+
taxTotal: S,
|
|
2803
|
+
total: S,
|
|
2804
|
+
depositAmount: S,
|
|
2805
|
+
shippingMode: S,
|
|
2806
|
+
issuedAt: S,
|
|
2807
|
+
expiresAt: S,
|
|
2808
|
+
paymentTerms: { mode: S, netN: S, balanceNetN: S, deposit: { type: S, value: S } },
|
|
2809
|
+
items: [{
|
|
2810
|
+
type: S,
|
|
2811
|
+
quantity: S,
|
|
2812
|
+
unitPrice: S,
|
|
2813
|
+
subtotal: S,
|
|
2814
|
+
discountAmount: S,
|
|
2815
|
+
taxAmount: S,
|
|
2816
|
+
total: S,
|
|
2817
|
+
optional: S,
|
|
2818
|
+
optionalSelected: S
|
|
2819
|
+
}]
|
|
2820
|
+
}],
|
|
2821
|
+
// Who commented and when, never what they wrote.
|
|
2822
|
+
comments: [{ authorType: S, visibility: S, createdAt: S }],
|
|
2823
|
+
timeline: [{ type: S, actorType: S, createdAt: S }]
|
|
2824
|
+
};
|
|
2825
|
+
var INVOICE = {
|
|
2826
|
+
id: S,
|
|
2827
|
+
number: S,
|
|
2828
|
+
status: S,
|
|
2829
|
+
sourceType: S,
|
|
2830
|
+
sourceId: S,
|
|
2831
|
+
customerId: S,
|
|
2832
|
+
currency: S,
|
|
2833
|
+
subtotal: S,
|
|
2834
|
+
discountTotal: S,
|
|
2835
|
+
shippingTotal: S,
|
|
2836
|
+
taxTotal: S,
|
|
2837
|
+
total: S,
|
|
2838
|
+
amountRefunded: S,
|
|
2839
|
+
issuedAt: S,
|
|
2840
|
+
dueAt: S,
|
|
2841
|
+
periodStart: S,
|
|
2842
|
+
periodEnd: S,
|
|
2843
|
+
createdAt: S,
|
|
2844
|
+
billingSnapshot: {
|
|
2845
|
+
planName: T,
|
|
2846
|
+
period: { start: S, end: S },
|
|
2847
|
+
lineItems: [{ quantity: S, unitPrice: S, discountAmount: S, taxAmount: S, total: S }],
|
|
2848
|
+
totals: { currency: S, subtotal: S, discountTotal: S, shippingTotal: S, taxTotal: S, total: S },
|
|
2849
|
+
payment: { method: S, brand: S, status: S }
|
|
2850
|
+
}
|
|
2851
|
+
};
|
|
2852
|
+
var DISCOUNT = {
|
|
2853
|
+
id: S,
|
|
2854
|
+
code: T,
|
|
2855
|
+
name: T,
|
|
2856
|
+
type: S,
|
|
2857
|
+
value: S,
|
|
2858
|
+
isActive: S,
|
|
2859
|
+
startsAt: S,
|
|
2860
|
+
endsAt: S,
|
|
2861
|
+
usageCount: S,
|
|
2862
|
+
usageLimit: S,
|
|
2863
|
+
maxRedemptionsPerCustomer: S,
|
|
2864
|
+
createdAt: S
|
|
2865
|
+
};
|
|
2866
|
+
var EVENT = {
|
|
2867
|
+
id: S,
|
|
2868
|
+
type: S,
|
|
2869
|
+
entityType: S,
|
|
2870
|
+
entityId: S,
|
|
2871
|
+
createdAt: S,
|
|
2872
|
+
data: {
|
|
2873
|
+
status: S,
|
|
2874
|
+
previousStatus: S,
|
|
2875
|
+
action: S,
|
|
2876
|
+
amount: S,
|
|
2877
|
+
currency: S,
|
|
2878
|
+
attempt: S,
|
|
2879
|
+
orderId: S,
|
|
2880
|
+
paymentId: S,
|
|
2881
|
+
order: { id: S, orderNumber: S, status: S, paymentStatus: S, currency: S, total: S },
|
|
2882
|
+
payment: { id: S, orderId: S, status: S, method: S, amount: S, currency: S },
|
|
2883
|
+
subscription: { id: S, status: S, planName: T, amount: S, currency: S, interval: S },
|
|
2884
|
+
fulfillment: { id: S, orderId: S, type: S, status: S }
|
|
2885
|
+
}
|
|
2886
|
+
};
|
|
2887
|
+
var DELIVERY = {
|
|
2888
|
+
id: S,
|
|
2889
|
+
endpointId: S,
|
|
2890
|
+
eventId: S,
|
|
2891
|
+
eventType: S,
|
|
2892
|
+
status: S,
|
|
2893
|
+
attemptCount: S,
|
|
2894
|
+
lastStatus: S,
|
|
2895
|
+
deliveredAt: S,
|
|
2896
|
+
nextRetryAt: S,
|
|
2897
|
+
createdAt: S,
|
|
2898
|
+
replayOf: S
|
|
2899
|
+
};
|
|
2900
|
+
var MONEY_COUNT = { count: S, totalCents: S };
|
|
2901
|
+
var ORDER_PERIOD = { orderCount: S, grossCents: S, netCents: S, refundsCents: S, avgOrderCents: S };
|
|
2902
|
+
var OAUTH_PROJECTIONS = {
|
|
2903
|
+
whoami: {
|
|
2904
|
+
type: S,
|
|
2905
|
+
workspace: { name: T, slug: S },
|
|
2906
|
+
application: APPLICATION,
|
|
2907
|
+
environment: { slug: S, kind: S, providerEnvironment: S },
|
|
2908
|
+
note: T
|
|
2909
|
+
},
|
|
2910
|
+
search_docs: PASS,
|
|
2911
|
+
describe_endpoint: PASS,
|
|
2912
|
+
validate_payload: PASS,
|
|
2913
|
+
search_orders: page2(ORDER),
|
|
2914
|
+
get_order: {
|
|
2915
|
+
order: ORDER,
|
|
2916
|
+
payments: section([PAYMENT]),
|
|
2917
|
+
returns: section([RETURN]),
|
|
2918
|
+
fulfillments: section([FULFILLMENT])
|
|
2919
|
+
},
|
|
2920
|
+
search_customers: page2(CUSTOMER),
|
|
2921
|
+
get_customer: {
|
|
2922
|
+
customer: CUSTOMER,
|
|
2923
|
+
addresses: section([{ id: S, isDefault: S, ...ADDRESS }]),
|
|
2924
|
+
subscriptions: section([SUBSCRIPTION])
|
|
2925
|
+
},
|
|
2926
|
+
list_subscriptions: page2(SUBSCRIPTION),
|
|
2927
|
+
get_subscription: SUBSCRIPTION,
|
|
2928
|
+
search_quotes: page2(QUOTE_SUMMARY),
|
|
2929
|
+
get_quote: QUOTE,
|
|
2930
|
+
list_invoices: page2(INVOICE),
|
|
2931
|
+
get_invoice: INVOICE,
|
|
2932
|
+
receivables_aging: {
|
|
2933
|
+
buckets: [{ key: S, label: T, count: S, total_amount: S }],
|
|
2934
|
+
total_outstanding_amount: S,
|
|
2935
|
+
total_outstanding_count: S
|
|
2936
|
+
},
|
|
2937
|
+
search_discounts: page2(DISCOUNT),
|
|
2938
|
+
get_discount: DISCOUNT,
|
|
2939
|
+
search_payments: page2(PAYMENT),
|
|
2940
|
+
get_payment: {
|
|
2941
|
+
payment: PAYMENT,
|
|
2942
|
+
transactions: section([{ id: S, type: S, status: S, amount: S, currency: S, createdAt: S }])
|
|
2943
|
+
},
|
|
2944
|
+
list_abandoned_carts: {
|
|
2945
|
+
carts: section([{
|
|
2946
|
+
id: S,
|
|
2947
|
+
abandonedAt: S,
|
|
2948
|
+
currency: S,
|
|
2949
|
+
itemCount: S,
|
|
2950
|
+
recoveryStatus: S,
|
|
2951
|
+
total: S,
|
|
2952
|
+
customer: { id: S }
|
|
2953
|
+
}]),
|
|
2954
|
+
summary: section({ abandonedCount: S, abandonedValue: S, recoveryEmailsSent: S, sinceDays: S })
|
|
2955
|
+
},
|
|
2956
|
+
search_events: page2(EVENT),
|
|
2957
|
+
list_webhook_deliveries: page2(DELIVERY),
|
|
2958
|
+
get_webhook_delivery: DELIVERY,
|
|
2959
|
+
explain_checkout_failure: {
|
|
2960
|
+
note: T,
|
|
2961
|
+
hint: T,
|
|
2962
|
+
session: {
|
|
2963
|
+
id: S,
|
|
2964
|
+
status: S,
|
|
2965
|
+
cartId: S,
|
|
2966
|
+
customerId: S,
|
|
2967
|
+
expiresAt: S,
|
|
2968
|
+
completedAt: S,
|
|
2969
|
+
allowedMethods: [S],
|
|
2970
|
+
collect: { shippingAddress: S, billingAddress: S, vatNumber: S },
|
|
2971
|
+
shippingAddress: ADDRESS,
|
|
2972
|
+
billingAddress: ADDRESS,
|
|
2973
|
+
recurringTerms: {
|
|
2974
|
+
interval: S,
|
|
2975
|
+
intervalUnit: S,
|
|
2976
|
+
intervalCount: S,
|
|
2977
|
+
totalPayments: S,
|
|
2978
|
+
taxApplies: S,
|
|
2979
|
+
planChargedToday: S,
|
|
2980
|
+
planName: T,
|
|
2981
|
+
trialDays: S,
|
|
2982
|
+
currency: S,
|
|
2983
|
+
firstChargeAmountMinor: S
|
|
2984
|
+
}
|
|
2985
|
+
},
|
|
2986
|
+
cart: section({
|
|
2987
|
+
id: S,
|
|
2988
|
+
status: S,
|
|
2989
|
+
currency: S,
|
|
2990
|
+
subtotal: S,
|
|
2991
|
+
discountTotal: S,
|
|
2992
|
+
shippingTotal: S,
|
|
2993
|
+
taxTotal: S,
|
|
2994
|
+
total: S,
|
|
2995
|
+
itemCount: S,
|
|
2996
|
+
items: [LINE_ITEM],
|
|
2997
|
+
lineItems: [LINE_ITEM]
|
|
2998
|
+
}),
|
|
2999
|
+
events: [EVENT],
|
|
3000
|
+
eventsUnavailable: T,
|
|
3001
|
+
recentWebhookDeliveries: section([DELIVERY])
|
|
3002
|
+
},
|
|
3003
|
+
list_extension_installations: page2({
|
|
3004
|
+
id: S,
|
|
3005
|
+
extensionId: S,
|
|
3006
|
+
extensionName: T,
|
|
3007
|
+
extensionSlug: S,
|
|
3008
|
+
versionId: S,
|
|
3009
|
+
status: S,
|
|
3010
|
+
environmentSlug: S,
|
|
3011
|
+
installedAt: S,
|
|
3012
|
+
uninstalledAt: S
|
|
3013
|
+
}),
|
|
3014
|
+
get_connector_routing: {
|
|
3015
|
+
paymentRouting: section({
|
|
3016
|
+
defaultCascade: [S],
|
|
3017
|
+
rules: [{ cascade: [S], conditions: [{ type: S, op: S, currency: S, min: S, max: S, values: [S] }] }]
|
|
3018
|
+
}),
|
|
3019
|
+
cardDecline: section({ rules: [{ errorCode: S, conditions: [{ type: S, op: S, values: [S] }] }] }),
|
|
3020
|
+
otherDecline: section({ rules: [{ errorCode: S, conditions: [{ type: S, op: S, values: [S] }] }] }),
|
|
3021
|
+
routingConnectors: section([{ id: S, displayName: T, method: S }])
|
|
3022
|
+
},
|
|
3023
|
+
get_integration_status: {
|
|
3024
|
+
environment: { slug: S, kind: S, providerEnvironment: S },
|
|
3025
|
+
workspace: { name: T, slug: S },
|
|
3026
|
+
applicationId: S,
|
|
3027
|
+
application: APPLICATION,
|
|
3028
|
+
connectors: section([{
|
|
3029
|
+
id: S,
|
|
3030
|
+
displayName: T,
|
|
3031
|
+
method: S,
|
|
3032
|
+
type: S,
|
|
3033
|
+
status: S,
|
|
3034
|
+
active: S,
|
|
3035
|
+
position: S,
|
|
3036
|
+
acceptedCountries: [S],
|
|
3037
|
+
acceptedCurrencies: [S],
|
|
3038
|
+
threeDSecureEnabled: S,
|
|
3039
|
+
createdAt: S
|
|
3040
|
+
}]),
|
|
3041
|
+
webhookEndpoints: section([{ id: S, isActive: S, enabledEvents: [S], createdAt: S }]),
|
|
3042
|
+
unsubscribedEvents: section({ windowDays: S, unsubscribed: [{ type: S, count: S, lastEmittedAt: S }] })
|
|
3043
|
+
},
|
|
3044
|
+
get_workspace_settings: { name: T, slug: S, brandColor: S, logoUrl: T, onboardingCompletedAt: S },
|
|
3045
|
+
// What happened and to which record; not which team member did it.
|
|
3046
|
+
get_audit_log: page2({ id: S, action: S, resourceType: S, resourceId: S, createdAt: S }),
|
|
3047
|
+
list_applications: [{ id: S, name: T, slug: S, currency: S, isActive: S }],
|
|
3048
|
+
// Analytics: computed aggregates, listed field by field all the same.
|
|
3049
|
+
get_business_summary: {
|
|
3050
|
+
period: S,
|
|
3051
|
+
range: RANGE,
|
|
3052
|
+
orders: section({ current: ORDER_PERIOD, previous: ORDER_PERIOD, deltaPct: S }),
|
|
3053
|
+
subscriptions: section({
|
|
3054
|
+
mrr: { currentCents: S, periodStartCents: S, deltaPct: S },
|
|
3055
|
+
newSubscriptions: S,
|
|
3056
|
+
cancelledSubscriptions: S,
|
|
3057
|
+
churnRate: S,
|
|
3058
|
+
note: T
|
|
3059
|
+
}),
|
|
3060
|
+
receivables: section({ outstandingCents: S, outstandingCount: S }),
|
|
3061
|
+
quotes: section({ sent: MONEY_COUNT, accepted: MONEY_COUNT, declined: MONEY_COUNT, expired: MONEY_COUNT })
|
|
3062
|
+
},
|
|
3063
|
+
get_attention_digest: {
|
|
3064
|
+
disputes: section({ open: S, totalCents: S }),
|
|
3065
|
+
subscriptionsInDunning: section(S),
|
|
3066
|
+
overdueInvoices: section(MONEY_COUNT),
|
|
3067
|
+
unhealthyWebhooks: section([{ endpointId: S, failureRate: S, attempts7d: S }])
|
|
3068
|
+
},
|
|
3069
|
+
get_sales_timeseries: {
|
|
3070
|
+
application: APPLICATION,
|
|
3071
|
+
range: RANGE,
|
|
3072
|
+
currency: S,
|
|
3073
|
+
days: S,
|
|
3074
|
+
note: T,
|
|
3075
|
+
series: [{ date: S, grossCents: S, orders: S }],
|
|
3076
|
+
totalGrossCents: S,
|
|
3077
|
+
totalOrders: S
|
|
3078
|
+
},
|
|
3079
|
+
// get_top_products has no entry, so it is not offered over OAuth: it ranks
|
|
3080
|
+
// products by line-item reference id or name, which a buyer can type.
|
|
3081
|
+
get_customer_growth: {
|
|
3082
|
+
application: APPLICATION,
|
|
3083
|
+
range: RANGE,
|
|
3084
|
+
note: T,
|
|
3085
|
+
topBy: S,
|
|
3086
|
+
signups: { total: S, series: [{ date: S, signups: S }] },
|
|
3087
|
+
ordersByCustomerType: { newCustomerOrders: S, returningCustomerOrders: S },
|
|
3088
|
+
// The ranking, by id: a top customer's name and email stay behind.
|
|
3089
|
+
topCustomers: [{ customerId: S, orderCount: S, revenueCents: S }],
|
|
3090
|
+
totals: { customerCount: S, orderCount: S, revenueCents: S }
|
|
3091
|
+
},
|
|
3092
|
+
get_revenue_breakdown: {
|
|
3093
|
+
application: APPLICATION,
|
|
3094
|
+
range: RANGE,
|
|
3095
|
+
dimension: S,
|
|
3096
|
+
note: T,
|
|
3097
|
+
rows: [{ key: S, orderCount: S, grossCents: S }],
|
|
3098
|
+
totals: { orderCount: S, grossCents: S }
|
|
3099
|
+
},
|
|
3100
|
+
get_funnel_conversion: {
|
|
3101
|
+
application: APPLICATION,
|
|
3102
|
+
range: RANGE,
|
|
3103
|
+
note: T,
|
|
3104
|
+
conversionRate: S,
|
|
3105
|
+
stages: [{ name: S, count: S }],
|
|
3106
|
+
dropOff: [{ from: S, to: S, ratePct: S }]
|
|
3107
|
+
},
|
|
3108
|
+
get_subscription_movement: {
|
|
3109
|
+
application: APPLICATION,
|
|
3110
|
+
range: RANGE,
|
|
3111
|
+
note: T,
|
|
3112
|
+
series: [{ date: S, newSubscriptions: S, churnedSubscriptions: S, mrrDeltaCents: S }],
|
|
3113
|
+
totals: { newSubscriptions: S, churnedSubscriptions: S, netMrrDeltaCents: S }
|
|
3114
|
+
},
|
|
3115
|
+
get_discount_performance: {
|
|
3116
|
+
application: APPLICATION,
|
|
3117
|
+
range: RANGE,
|
|
3118
|
+
note: T,
|
|
3119
|
+
rows: [{ discountId: S, code: T, redemptions: S, discountedRevenueCents: S, discountGivenCents: S }],
|
|
3120
|
+
totals: { redemptions: S, discountedRevenueCents: S, discountGivenCents: S }
|
|
3121
|
+
}
|
|
3122
|
+
};
|
|
3123
|
+
|
|
1552
3124
|
// src/server.ts
|
|
1553
|
-
var VERSION =
|
|
3125
|
+
var VERSION = true ? "0.2.2" : "0.0.0-dev";
|
|
1554
3126
|
function registerableActions(policy) {
|
|
1555
3127
|
const isProduction = policy.identity.environment.providerEnvironment === "production";
|
|
1556
3128
|
return actionsForChannel("mcp").filter((action) => {
|
|
1557
3129
|
if (!HANDLERS[action.id]) return false;
|
|
3130
|
+
if (action.credentialScoped && policy.allowCredentialScoped === false) return false;
|
|
1558
3131
|
if (!canRunAction(policy.identity.scopes, action)) return false;
|
|
1559
3132
|
if (action.kind === "read") return true;
|
|
1560
|
-
if (action.sandboxOnly) return !isProduction;
|
|
3133
|
+
if (action.sandboxOnly) return !isProduction && (policy.allowSandboxWrites ?? true);
|
|
1561
3134
|
if (!policy.allowWrites) return false;
|
|
1562
3135
|
return !isProduction || policy.allowLiveWrites;
|
|
1563
3136
|
});
|
|
@@ -1574,11 +3147,14 @@ function buildInstructions(identity, actions, policy) {
|
|
|
1574
3147
|
`Throttle MCP ${VERSION}. Connected to ${where}, environment "${identity.environment.slug}" (${isProduction ? "PRODUCTION \u2014 live data" : "non-production \u2014 not live data"}).`,
|
|
1575
3148
|
"",
|
|
1576
3149
|
"Conventions:",
|
|
1577
|
-
"-
|
|
3150
|
+
"- 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.",
|
|
3151
|
+
'- 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.',
|
|
3152
|
+
"- 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.",
|
|
1578
3153
|
'- List tools return { data, pagination }. When pagination.hasMore is true, pass pagination.cursor back as "cursor" rather than raising "limit".',
|
|
1579
3154
|
"- Amounts are integer minor units (2500 = $25.00).",
|
|
1580
3155
|
"- 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.",
|
|
1581
|
-
"- API errors come back as text with a stable code. On insufficient_scopes, tell the user which scope to add \u2014 do not retry."
|
|
3156
|
+
"- API errors come back as text with a stable code. On insufficient_scopes, tell the user which scope to add \u2014 do not retry.",
|
|
3157
|
+
"- 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."
|
|
1582
3158
|
];
|
|
1583
3159
|
if (liveWrites.length === 0) {
|
|
1584
3160
|
lines.push(
|
|
@@ -1596,8 +3172,31 @@ function buildInstructions(identity, actions, policy) {
|
|
|
1596
3172
|
}
|
|
1597
3173
|
return lines.join("\n");
|
|
1598
3174
|
}
|
|
3175
|
+
function declareSecuritySchemes(server, actions) {
|
|
3176
|
+
const inner = server.server;
|
|
3177
|
+
const handlers = inner._requestHandlers;
|
|
3178
|
+
const original = handlers?.get("tools/list");
|
|
3179
|
+
if (!original) return;
|
|
3180
|
+
const schemes = new Map(
|
|
3181
|
+
actions.map((a) => [
|
|
3182
|
+
a.id,
|
|
3183
|
+
[{
|
|
3184
|
+
type: "oauth2",
|
|
3185
|
+
scopes: [.../* @__PURE__ */ new Set([...a.scopes, ...a.sectionScopes ?? []])].filter((scope) => validateScopesForOAuth([scope]).ok)
|
|
3186
|
+
}]
|
|
3187
|
+
])
|
|
3188
|
+
);
|
|
3189
|
+
inner.setRequestHandler(ListToolsRequestSchema, async (request, extra) => {
|
|
3190
|
+
const result = await original(request, extra);
|
|
3191
|
+
return {
|
|
3192
|
+
...result,
|
|
3193
|
+
tools: result.tools.map((tool) => ({ ...tool, securitySchemes: schemes.get(tool.name) ?? [{ type: "oauth2", scopes: [] }] }))
|
|
3194
|
+
};
|
|
3195
|
+
});
|
|
3196
|
+
}
|
|
1599
3197
|
function createServer(options) {
|
|
1600
|
-
const
|
|
3198
|
+
const redactPii = options.identity.type === "oauth_token" && !hasScope(options.identity.scopes, "customer_pii:read");
|
|
3199
|
+
const actions = registerableActions(options).filter((a) => !redactPii || OAUTH_PROJECTIONS[a.id] !== void 0);
|
|
1601
3200
|
const server = new McpServer(
|
|
1602
3201
|
{ name: "throttle", version: VERSION },
|
|
1603
3202
|
{ instructions: buildInstructions(options.identity, actions, options) }
|
|
@@ -1610,25 +3209,57 @@ function createServer(options) {
|
|
|
1610
3209
|
};
|
|
1611
3210
|
for (const action of actions) {
|
|
1612
3211
|
const handler = HANDLERS[action.id];
|
|
1613
|
-
server.
|
|
3212
|
+
server.registerTool(
|
|
1614
3213
|
action.id,
|
|
1615
|
-
|
|
1616
|
-
|
|
3214
|
+
{
|
|
3215
|
+
title: action.title,
|
|
3216
|
+
description: describeForModel(action),
|
|
3217
|
+
inputSchema: fieldsToZodShape(action.input),
|
|
3218
|
+
// Every hint is an explicit boolean: directory reviews (OpenAI's in
|
|
3219
|
+
// particular) read a missing value as "unknown", not as false.
|
|
3220
|
+
annotations: {
|
|
3221
|
+
title: action.title,
|
|
3222
|
+
readOnlyHint: action.kind === "read",
|
|
3223
|
+
destructiveHint: action.kind === "write" && Boolean(action.destructive),
|
|
3224
|
+
idempotentHint: Boolean(action.idempotent),
|
|
3225
|
+
// Every tool is bounded to the connected workspace; none reaches the
|
|
3226
|
+
// public internet or an open-ended set of external entities.
|
|
3227
|
+
openWorldHint: false
|
|
3228
|
+
}
|
|
3229
|
+
},
|
|
1617
3230
|
async (args) => {
|
|
1618
3231
|
try {
|
|
3232
|
+
if (redactPii && action.id === "search_customers" && typeof args?.q === "string" && args.q.trim() !== "") {
|
|
3233
|
+
return {
|
|
3234
|
+
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." }],
|
|
3235
|
+
isError: true
|
|
3236
|
+
};
|
|
3237
|
+
}
|
|
1619
3238
|
const result = await handler(ctx, args ?? {});
|
|
1620
|
-
|
|
3239
|
+
const shown = redactPii ? redactShopperPii(project(result, OAUTH_PROJECTIONS[action.id])) : result;
|
|
3240
|
+
return { content: [{ type: "text", text: formatResult(shown) }] };
|
|
1621
3241
|
} catch (error) {
|
|
1622
3242
|
return {
|
|
1623
3243
|
content: [{ type: "text", text: formatError(error) }],
|
|
1624
|
-
isError: true
|
|
3244
|
+
isError: true,
|
|
3245
|
+
...options.authChallenge && isCredentialRejection(error) ? {
|
|
3246
|
+
_meta: {
|
|
3247
|
+
"mcp/www_authenticate": [
|
|
3248
|
+
`${options.authChallenge}, error="invalid_token", error_description="The connected credential was rejected. Sign in again to continue."`
|
|
3249
|
+
]
|
|
3250
|
+
}
|
|
3251
|
+
} : {}
|
|
1625
3252
|
};
|
|
1626
3253
|
}
|
|
1627
3254
|
}
|
|
1628
3255
|
);
|
|
1629
3256
|
}
|
|
3257
|
+
if (options.authChallenge) declareSecuritySchemes(server, actions);
|
|
1630
3258
|
return server;
|
|
1631
3259
|
}
|
|
3260
|
+
function isCredentialRejection(error) {
|
|
3261
|
+
return error?.statusCode === 401;
|
|
3262
|
+
}
|
|
1632
3263
|
function describeForModel(action) {
|
|
1633
3264
|
const parts = [action.description];
|
|
1634
3265
|
if (action.guardrails && action.guardrails.length > 0) {
|