@usethrottle/mcp 0.1.1 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -2
- package/dist/contract-index.json +1 -1
- package/dist/docs-index.json +1 -1
- package/dist/lib.cjs +3328 -0
- package/dist/lib.cjs.map +1 -0
- package/dist/lib.d.cts +219 -0
- package/dist/lib.d.ts +219 -0
- package/dist/lib.js +3294 -0
- package/dist/lib.js.map +1 -0
- package/dist/mcp.js +2053 -142
- package/dist/mcp.js.map +1 -1
- package/package.json +11 -1
package/dist/mcp.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
import { readFileSync } from "fs";
|
|
5
4
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
6
5
|
|
|
7
6
|
// ../errors/dist/index.js
|
|
@@ -38,18 +37,43 @@ function buildQuery(query) {
|
|
|
38
37
|
const qs = params.toString();
|
|
39
38
|
return qs ? `?${qs}` : "";
|
|
40
39
|
}
|
|
40
|
+
var API_KEY_PREFIX_RE = /^(sk_|pk_)/;
|
|
41
41
|
var ThrottleClient = class {
|
|
42
42
|
constructor(auth, fetchImpl = fetch) {
|
|
43
43
|
this.auth = auth;
|
|
44
44
|
this.fetchImpl = fetchImpl;
|
|
45
45
|
}
|
|
46
|
+
/**
|
|
47
|
+
* Swap the credential every later request is sent with. The hosted remote
|
|
48
|
+
* server builds one client per MCP session at `initialize`, but an OAuth
|
|
49
|
+
* access token lives one hour and the MCP client refreshes it underneath
|
|
50
|
+
* the session — each request then arrives with a newer bearer than the one
|
|
51
|
+
* the session was opened with. Without this, the session keeps calling the
|
|
52
|
+
* REST API with the token it was born with and every tool fails with a 401
|
|
53
|
+
* the moment that first token expires, even though the caller is still
|
|
54
|
+
* perfectly authenticated.
|
|
55
|
+
*/
|
|
56
|
+
setApiKey(apiKey) {
|
|
57
|
+
this.auth = { ...this.auth, apiKey };
|
|
58
|
+
}
|
|
46
59
|
async requestJson(method, path, options = {}) {
|
|
47
60
|
const url = `${this.auth.apiBaseUrl.replace(/\/$/, "")}${path}${buildQuery(options.query)}`;
|
|
48
61
|
const headers = {
|
|
49
62
|
"content-type": "application/json",
|
|
50
|
-
"x-api-key": this.auth.apiKey
|
|
63
|
+
"x-api-key": this.auth.apiKey,
|
|
64
|
+
// Also sent as Authorization: Bearer — required for an OAuth access
|
|
65
|
+
// token (the remote server's oauth_token sessions), which the auth
|
|
66
|
+
// middleware only ever recognizes via this header (peeked by issuer),
|
|
67
|
+
// never via x-api-key. For an sk_/pk_ key this is a no-op: the
|
|
68
|
+
// middleware already treats `Authorization: Bearer sk_.../pk_...`
|
|
69
|
+
// identically to `X-API-Key`, so existing stdio-CLI callers are
|
|
70
|
+
// unaffected.
|
|
71
|
+
Authorization: `Bearer ${this.auth.apiKey}`
|
|
51
72
|
};
|
|
52
73
|
if (options.idempotencyKey) headers["idempotency-key"] = options.idempotencyKey;
|
|
74
|
+
if (options.applicationId && !API_KEY_PREFIX_RE.test(this.auth.apiKey)) {
|
|
75
|
+
headers["x-throttle-application-id"] = options.applicationId;
|
|
76
|
+
}
|
|
53
77
|
const sendsBody = method === "POST" || method === "PATCH" || method === "PUT";
|
|
54
78
|
const body = options.body !== void 0 ? JSON.stringify(options.body) : sendsBody ? "{}" : void 0;
|
|
55
79
|
const res = await this.fetchImpl(url, { method, headers, body });
|
|
@@ -71,12 +95,12 @@ var ThrottleClient = class {
|
|
|
71
95
|
return parsed;
|
|
72
96
|
}
|
|
73
97
|
/** Unwraps the `{ data }` envelope. Key presence, not `??` — `{data: null}` unwraps to null. */
|
|
74
|
-
async get(path, query) {
|
|
75
|
-
const body = await this.requestJson("GET", path, { query });
|
|
98
|
+
async get(path, query, options) {
|
|
99
|
+
const body = await this.requestJson("GET", path, { query, applicationId: options?.applicationId });
|
|
76
100
|
return body && typeof body === "object" && "data" in body ? body.data : body;
|
|
77
101
|
}
|
|
78
|
-
async list(path, query) {
|
|
79
|
-
const body = await this.requestJson("GET", path, { query });
|
|
102
|
+
async list(path, query, options) {
|
|
103
|
+
const body = await this.requestJson("GET", path, { query, applicationId: options?.applicationId });
|
|
80
104
|
const data = "data" in body ? body.data : body;
|
|
81
105
|
const meta = body.meta;
|
|
82
106
|
return { data: Array.isArray(data) ? data : [], pagination: meta?.pagination ?? null };
|
|
@@ -85,6 +109,10 @@ var ThrottleClient = class {
|
|
|
85
109
|
const body = await this.requestJson("POST", path, options);
|
|
86
110
|
return body && typeof body === "object" && "data" in body ? body.data : body;
|
|
87
111
|
}
|
|
112
|
+
async patch(path, options = {}) {
|
|
113
|
+
const body = await this.requestJson("PATCH", path, options);
|
|
114
|
+
return body && typeof body === "object" && "data" in body ? body.data : body;
|
|
115
|
+
}
|
|
88
116
|
};
|
|
89
117
|
async function optional(load) {
|
|
90
118
|
try {
|
|
@@ -99,6 +127,7 @@ async function optional(load) {
|
|
|
99
127
|
|
|
100
128
|
// src/server.ts
|
|
101
129
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
130
|
+
import { ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
102
131
|
|
|
103
132
|
// ../core-scopes/src/scopes.ts
|
|
104
133
|
function hasScope(granted, required) {
|
|
@@ -127,6 +156,8 @@ function add(group, resource, actions, labels, opts = {}) {
|
|
|
127
156
|
staffOnly: opts.staffOnly ?? false,
|
|
128
157
|
// publishable defaults false; only the curated set below is true
|
|
129
158
|
publishableAllowed: opts.publishableAllowed ?? false,
|
|
159
|
+
// publishableOnly defaults false; only customer_accounts:write sets it
|
|
160
|
+
publishableOnly: opts.publishableOnly ?? false,
|
|
130
161
|
// extension default true for commerce scopes; Taxonomy B passes false
|
|
131
162
|
extensionAllowed: opts.extensionAllowed ?? true,
|
|
132
163
|
implies
|
|
@@ -137,19 +168,36 @@ add("Orders", "orders", ["read", "write"], { read: "View orders", write: "Manage
|
|
|
137
168
|
add("Quotes", "quotes", ["read", "write"], { read: "View quotes", write: "Manage quotes" });
|
|
138
169
|
add("Orders", "order_cancellations", ["write"], { write: "Cancel orders" });
|
|
139
170
|
add("Orders", "order_returns", ["read", "write"], { read: "View returns", write: "Process returns" });
|
|
171
|
+
add("Orders", "order_comps", ["write"], { write: "Comp orders (write off the balance)" }, { sensitive: true });
|
|
172
|
+
add("Orders", "order_holds", ["write"], { write: "Freeze and unfreeze orders" });
|
|
173
|
+
add(
|
|
174
|
+
"Imports",
|
|
175
|
+
"imports",
|
|
176
|
+
["read", "write"],
|
|
177
|
+
{ read: "View data imports", write: "Run and undo data imports" },
|
|
178
|
+
{ extensionAllowed: false }
|
|
179
|
+
);
|
|
140
180
|
add("Payments", "payments", ["read", "write"], { read: "View payments", write: "Create payments" });
|
|
141
181
|
add("Payments", "payment_authorizations", ["write"], { write: "Authorize payments" });
|
|
142
182
|
add("Payments", "payment_captures", ["write"], { write: "Capture payments" });
|
|
143
183
|
add("Payments", "payment_refunds", ["read", "write"], { read: "View refunds", write: "Refund payments" }, { sensitive: true });
|
|
144
184
|
add("Payments", "payment_voids", ["write"], { write: "Void payments" }, { sensitive: true });
|
|
185
|
+
add("Payments", "payment_records", ["write"], { write: "Record payments received outside Throttle" }, { sensitive: true });
|
|
145
186
|
add("Payments", "payment_disputes", ["read", "write"], { read: "View disputes", write: "Handle disputes" }, { sensitive: true });
|
|
146
187
|
add("Payments", "payment_methods", ["read", "write"], { read: "View payment methods", write: "Tokenize payment methods" }, { sensitive: true });
|
|
147
188
|
add("Customers", "customers", ["read", "write"], { read: "View customers", write: "Manage customers" });
|
|
148
189
|
add("Customers", "customer_pii", ["read"], { read: "View customer PII" }, { sensitive: true });
|
|
149
190
|
add("Customers", "customer_addresses", ["read", "write"], { read: "View addresses", write: "Manage addresses" });
|
|
150
191
|
add("Customers", "customer_payment_methods", ["read", "write"], { read: "View stored cards", write: "Manage stored cards" }, { sensitive: true });
|
|
192
|
+
add(
|
|
193
|
+
"Customer accounts",
|
|
194
|
+
"customer_accounts",
|
|
195
|
+
["write"],
|
|
196
|
+
{ write: "Let shoppers register, sign in and manage their account" },
|
|
197
|
+
{ publishableAllowed: true, publishableOnly: true, extensionAllowed: false }
|
|
198
|
+
);
|
|
151
199
|
add("Carts", "carts", ["read", "write"], { read: "View carts", write: "Manage carts" });
|
|
152
|
-
add("Carts", "cart_items", ["
|
|
200
|
+
add("Carts", "cart_items", ["write"], { write: "Manage line items" });
|
|
153
201
|
add("Carts", "checkout_sessions", ["read", "write"], { read: "View checkout sessions", write: "Manage checkout sessions" });
|
|
154
202
|
add("Discounts", "discounts", ["read", "write"], { read: "View discounts", write: "Manage discounts" });
|
|
155
203
|
add("Subscriptions", "subscriptions", ["read", "write"], { read: "View subscriptions", write: "Manage subscriptions" });
|
|
@@ -170,8 +218,32 @@ add("Shipping & Tax", "shipping_quotes", ["write"], { write: "Request shipping q
|
|
|
170
218
|
add("Shipping & Tax", "tax_calculations", ["write"], { write: "Calculate tax" }, { publishableAllowed: true });
|
|
171
219
|
add("Shipping & Tax", "shipping_tax_quote_tokens", ["read", "write"], { read: "View quote tokens", write: "Manage quote tokens" });
|
|
172
220
|
add("Shipping & Tax", "shipping_tax_snapshots", ["write"], { write: "Accept external snapshots" });
|
|
221
|
+
add(
|
|
222
|
+
"Scripts",
|
|
223
|
+
"storefront_scripts",
|
|
224
|
+
["read", "write"],
|
|
225
|
+
{
|
|
226
|
+
read: "Load storefront scripts",
|
|
227
|
+
// `write` covers exactly one thing: reporting a script's own load outcome
|
|
228
|
+
// from the merchant's storefront, so the health panel stops being
|
|
229
|
+
// hosted-checkout-only. On checkout the session id in the URL is the
|
|
230
|
+
// capability that scopes that write; a storefront page has no session, so
|
|
231
|
+
// the pk it already holds is what scopes it instead. It writes nothing but
|
|
232
|
+
// load telemetry — authoring stays on `application_scripts:write`, which
|
|
233
|
+
// is not publishable-allowed.
|
|
234
|
+
write: "Report storefront script load outcomes"
|
|
235
|
+
},
|
|
236
|
+
{ publishableAllowed: true }
|
|
237
|
+
);
|
|
238
|
+
add("Scripts", "application_scripts", ["read", "write"], {
|
|
239
|
+
read: "View merchant-authored scripts",
|
|
240
|
+
write: "Create, edit and disable merchant-authored scripts"
|
|
241
|
+
}, { extensionAllowed: false });
|
|
242
|
+
add("Scripts", "extension_scripts", ["read", "write"], {
|
|
243
|
+
read: "View an extension's buyer-facing scripts",
|
|
244
|
+
write: "Create, edit and delete an extension's buyer-facing scripts"
|
|
245
|
+
}, { extensionAllowed: false });
|
|
173
246
|
add("Connectors", "connectors", ["read", "write"], { read: "View connectors", write: "Manage connectors" });
|
|
174
|
-
add("Connectors", "connector_activations", ["write"], { write: "Activate/deactivate connectors" });
|
|
175
247
|
add("Webhooks & Events", "webhooks", ["read", "write"], { read: "View webhooks", write: "Manage webhooks" });
|
|
176
248
|
add("Webhooks & Events", "webhook_deliveries", ["read", "write"], { read: "View deliveries", write: "Retry deliveries" });
|
|
177
249
|
add("Webhooks & Events", "events", ["read"], { read: "View events" });
|
|
@@ -187,7 +259,7 @@ add("Platform billing", "billing", ["read", "write"], { read: "View billing", wr
|
|
|
187
259
|
add("Platform billing", "billing_payment_methods", ["read", "write"], { read: "View billing cards", write: "Manage billing cards" }, HUMAN);
|
|
188
260
|
add("Email", "email_templates", ["read", "write"], { read: "View email templates", write: "Edit email templates" }, HUMAN);
|
|
189
261
|
add("Email", "email_settings", ["read", "write"], { read: "View email settings", write: "Edit email settings" }, HUMAN);
|
|
190
|
-
add("Go-live", "go_live", ["
|
|
262
|
+
add("Go-live", "go_live", ["write"], { write: "Submit go-live" }, HUMAN);
|
|
191
263
|
add("Partner", "partner", ["read", "write"], { read: "View partner profile", write: "Edit partner profile" }, HUMAN);
|
|
192
264
|
add("Partner", "affiliates", ["read", "write"], { read: "View affiliates", write: "Manage affiliates" }, HUMAN);
|
|
193
265
|
add("Notifications", "notifications", ["write"], { write: "Send notifications" }, HUMAN);
|
|
@@ -211,6 +283,7 @@ REG["extensions:install"] = {
|
|
|
211
283
|
sensitive: false,
|
|
212
284
|
staffOnly: false,
|
|
213
285
|
publishableAllowed: false,
|
|
286
|
+
publishableOnly: false,
|
|
214
287
|
extensionAllowed: false,
|
|
215
288
|
implies: []
|
|
216
289
|
};
|
|
@@ -223,6 +296,7 @@ REG["*"] = {
|
|
|
223
296
|
sensitive: true,
|
|
224
297
|
staffOnly: false,
|
|
225
298
|
publishableAllowed: false,
|
|
299
|
+
publishableOnly: false,
|
|
226
300
|
extensionAllowed: false,
|
|
227
301
|
implies: []
|
|
228
302
|
};
|
|
@@ -242,6 +316,9 @@ var EVENT_SCOPE_OVERRIDES = {
|
|
|
242
316
|
"payment.refunded": "payment_refunds:read",
|
|
243
317
|
"payment.partially_refunded": "payment_refunds:read",
|
|
244
318
|
"payment.refund_failed": "payment_refunds:read",
|
|
319
|
+
// Money returned, so it belongs with the other refund events rather than
|
|
320
|
+
// behind the broad subscriptions:read the `subscription.` prefix would give.
|
|
321
|
+
"subscription.invoice_refunded": "payment_refunds:read",
|
|
245
322
|
"payment.disputed": "payment_disputes:read",
|
|
246
323
|
"payment.dispute_cleared": "payment_disputes:read",
|
|
247
324
|
"fulfillment.digital.delivered": "fulfillment_digital:read"
|
|
@@ -258,9 +335,17 @@ var PREFIX_RULES = [
|
|
|
258
335
|
["fulfillment.", "fulfillments:read"],
|
|
259
336
|
["discount.", "discounts:read"],
|
|
260
337
|
["invoice.", "invoices:read"],
|
|
338
|
+
["import.", "imports:read"],
|
|
261
339
|
["shipping_tax.", "shipping_tax:read"],
|
|
262
340
|
["workspace.", "billing:read"],
|
|
263
|
-
|
|
341
|
+
// `extensions:read` is a human scope (extensions must never watch other
|
|
342
|
+
// extensions), so only a workspace endpoint can SUBSCRIBE to this family.
|
|
343
|
+
// The uninstalled installation's own endpoint is addressed directly by the
|
|
344
|
+
// uninstall path and needs no subscription at all.
|
|
345
|
+
["extension.", "extensions:read"],
|
|
346
|
+
// Subscribing to script outcomes needs script read — the payload names the
|
|
347
|
+
// script key and its hash, which is the merchant's script inventory.
|
|
348
|
+
["script.", "application_scripts:read"]
|
|
264
349
|
];
|
|
265
350
|
function eventReadScope(type) {
|
|
266
351
|
if (EVENT_SCOPE_OVERRIDES[type]) return EVENT_SCOPE_OVERRIDES[type];
|
|
@@ -272,8 +357,90 @@ function eventReadScope(type) {
|
|
|
272
357
|
|
|
273
358
|
// ../core-scopes/src/issuance.ts
|
|
274
359
|
var PUBLISHABLE_SCOPES = Object.values(SCOPE_REGISTRY).filter((d) => d.publishableAllowed).map((d) => d.id);
|
|
360
|
+
var CONNECTOR_GRANTABLE_SENSITIVE_SCOPES = ["customer_pii:read"];
|
|
361
|
+
function validateScopesForOAuth(scopes, opts = {}) {
|
|
362
|
+
const invalid = [];
|
|
363
|
+
for (const id of scopes) {
|
|
364
|
+
if (id === "*") {
|
|
365
|
+
invalid.push(id);
|
|
366
|
+
continue;
|
|
367
|
+
}
|
|
368
|
+
const def = SCOPE_REGISTRY[id];
|
|
369
|
+
if (!def || def.staffOnly) {
|
|
370
|
+
invalid.push(id);
|
|
371
|
+
continue;
|
|
372
|
+
}
|
|
373
|
+
if (def.sensitive && !(opts.connector && CONNECTOR_GRANTABLE_SENSITIVE_SCOPES.includes(id))) invalid.push(id);
|
|
374
|
+
}
|
|
375
|
+
return { ok: invalid.length === 0, invalid };
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// ../core-scopes/src/shopper-pii.ts
|
|
379
|
+
var PERSON_KEYS = /* @__PURE__ */ new Set([
|
|
380
|
+
"email",
|
|
381
|
+
"customerEmail",
|
|
382
|
+
"billingEmail",
|
|
383
|
+
"buyerEmail",
|
|
384
|
+
"authorEmail",
|
|
385
|
+
"authorName",
|
|
386
|
+
"phone",
|
|
387
|
+
"firstName",
|
|
388
|
+
"lastName",
|
|
389
|
+
"addressLine1",
|
|
390
|
+
"addressLine2",
|
|
391
|
+
"billingAddressLines",
|
|
392
|
+
"processorResponse",
|
|
393
|
+
"shareToken",
|
|
394
|
+
"company",
|
|
395
|
+
"externalId",
|
|
396
|
+
"externalCustomerId",
|
|
397
|
+
// Free-form: contents cannot be inspected for a person, so the field goes.
|
|
398
|
+
"metadata",
|
|
399
|
+
"notes",
|
|
400
|
+
"internalNotes",
|
|
401
|
+
"buyerNote",
|
|
402
|
+
"buyerReference",
|
|
403
|
+
"changeSummary",
|
|
404
|
+
"declinedReason",
|
|
405
|
+
"compReason",
|
|
406
|
+
"holdReason",
|
|
407
|
+
"body",
|
|
408
|
+
"acceptance",
|
|
409
|
+
"acceptanceSelections",
|
|
410
|
+
"clientContext",
|
|
411
|
+
"ipAddress",
|
|
412
|
+
"userAgent",
|
|
413
|
+
"vatNumber",
|
|
414
|
+
"buyerVatNumber",
|
|
415
|
+
"taxExemptionNumber"
|
|
416
|
+
]);
|
|
417
|
+
var PERSON_CONTAINERS = /* @__PURE__ */ new Set(["customer", "buyer"]);
|
|
418
|
+
function isPlainObject(value) {
|
|
419
|
+
if (value === null || typeof value !== "object") return false;
|
|
420
|
+
const proto = Object.getPrototypeOf(value);
|
|
421
|
+
return proto === Object.prototype || proto === null;
|
|
422
|
+
}
|
|
423
|
+
function walk(value, parentKey, depth) {
|
|
424
|
+
if (depth > 32) return value;
|
|
425
|
+
if (Array.isArray(value)) return value.map((item) => walk(item, parentKey, depth + 1));
|
|
426
|
+
if (!isPlainObject(value)) return value;
|
|
427
|
+
const out = {};
|
|
428
|
+
const isPerson = parentKey !== null && PERSON_CONTAINERS.has(parentKey);
|
|
429
|
+
for (const [key, child] of Object.entries(value)) {
|
|
430
|
+
if (PERSON_KEYS.has(key) || isPerson && key === "name") {
|
|
431
|
+
out[key] = child === void 0 ? child : null;
|
|
432
|
+
} else {
|
|
433
|
+
out[key] = walk(child, key, depth + 1);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
return out;
|
|
437
|
+
}
|
|
438
|
+
function redactShopperPii(value) {
|
|
439
|
+
return walk(value, null, 0);
|
|
440
|
+
}
|
|
275
441
|
|
|
276
442
|
// ../action-catalog/src/actions.ts
|
|
443
|
+
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.";
|
|
277
444
|
var ACTIONS = [
|
|
278
445
|
// ---------------------------------------------------------------- identity
|
|
279
446
|
{
|
|
@@ -290,7 +457,7 @@ var ACTIONS = [
|
|
|
290
457
|
{
|
|
291
458
|
id: "search_docs",
|
|
292
459
|
title: "Search the Throttle developer docs",
|
|
293
|
-
description: "Full-text search over the Throttle developer documentation. Returns titles, canonical
|
|
460
|
+
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.",
|
|
294
461
|
kind: "read",
|
|
295
462
|
scopes: [],
|
|
296
463
|
input: [
|
|
@@ -327,7 +494,7 @@ var ACTIONS = [
|
|
|
327
494
|
{
|
|
328
495
|
id: "validate_payload",
|
|
329
496
|
title: "Validate a request body against the API contract",
|
|
330
|
-
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.",
|
|
497
|
+
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.",
|
|
331
498
|
kind: "read",
|
|
332
499
|
scopes: [],
|
|
333
500
|
input: [
|
|
@@ -342,15 +509,22 @@ var ACTIONS = [
|
|
|
342
509
|
{
|
|
343
510
|
id: "api_get",
|
|
344
511
|
title: "Read any Throttle API endpoint",
|
|
345
|
-
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.",
|
|
512
|
+
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.",
|
|
346
513
|
kind: "read",
|
|
347
514
|
scopes: [],
|
|
515
|
+
credentialScoped: true,
|
|
348
516
|
input: [
|
|
349
517
|
{
|
|
350
518
|
name: "path",
|
|
351
519
|
type: "string",
|
|
352
520
|
required: true,
|
|
353
521
|
description: "Path beginning with /api/v1, query string included."
|
|
522
|
+
},
|
|
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."
|
|
354
528
|
}
|
|
355
529
|
],
|
|
356
530
|
requests: [],
|
|
@@ -372,11 +546,33 @@ var ACTIONS = [
|
|
|
372
546
|
{ name: "q", type: "string", required: false, description: "Free-text search." },
|
|
373
547
|
{ name: "status", type: "string", required: false, description: "Order status filter." },
|
|
374
548
|
{ name: "paymentStatus", type: "string", required: false, description: "Payment status filter." },
|
|
549
|
+
{
|
|
550
|
+
name: "createdAfter",
|
|
551
|
+
type: "string",
|
|
552
|
+
required: false,
|
|
553
|
+
description: "ISO 8601 lower bound on creation time, exclusive. For a single day, pass that day's midnight and the next day's midnight."
|
|
554
|
+
},
|
|
555
|
+
{
|
|
556
|
+
name: "createdBefore",
|
|
557
|
+
type: "string",
|
|
558
|
+
required: false,
|
|
559
|
+
description: "ISO 8601 upper bound on creation time, exclusive."
|
|
560
|
+
},
|
|
375
561
|
{ name: "limit", type: "number", required: false, description: "Page size." },
|
|
376
|
-
{ name: "cursor", type: "string", required: false, description: "Pagination cursor." }
|
|
562
|
+
{ name: "cursor", type: "string", required: false, description: "Pagination cursor." },
|
|
563
|
+
{
|
|
564
|
+
name: "applicationId",
|
|
565
|
+
type: "string",
|
|
566
|
+
required: false,
|
|
567
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
568
|
+
}
|
|
377
569
|
],
|
|
378
570
|
requests: [{ method: "GET", path: "/api/v1/orders" }],
|
|
379
|
-
channels: ["mcp", "zapier", "make", "n8n"]
|
|
571
|
+
channels: ["mcp", "zapier", "make", "n8n"],
|
|
572
|
+
guardrails: [
|
|
573
|
+
OAUTH_WITHHELD,
|
|
574
|
+
"Over an OAuth connection, q matches the order id, order number and status fields only."
|
|
575
|
+
]
|
|
380
576
|
},
|
|
381
577
|
{
|
|
382
578
|
id: "get_order",
|
|
@@ -384,7 +580,16 @@ var ACTIONS = [
|
|
|
384
580
|
description: "Full picture of a single order: the order itself plus its payments, returns and fulfillments in one response.",
|
|
385
581
|
kind: "read",
|
|
386
582
|
scopes: ["orders:read"],
|
|
387
|
-
|
|
583
|
+
sectionScopes: ["payments:read", "order_returns:read", "fulfillments:read"],
|
|
584
|
+
input: [
|
|
585
|
+
{ name: "id", type: "string", required: true, description: "Order id." },
|
|
586
|
+
{
|
|
587
|
+
name: "applicationId",
|
|
588
|
+
type: "string",
|
|
589
|
+
required: false,
|
|
590
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
591
|
+
}
|
|
592
|
+
],
|
|
388
593
|
requests: [
|
|
389
594
|
{ method: "GET", path: "/api/v1/orders/:id" },
|
|
390
595
|
{ method: "GET", path: "/api/v1/orders/:id/payments" },
|
|
@@ -393,23 +598,45 @@ var ACTIONS = [
|
|
|
393
598
|
],
|
|
394
599
|
channels: ["mcp"],
|
|
395
600
|
guardrails: [
|
|
396
|
-
"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."
|
|
601
|
+
"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.",
|
|
602
|
+
OAUTH_WITHHELD
|
|
397
603
|
]
|
|
398
604
|
},
|
|
399
605
|
// ---------------------------------------------------------------- customers
|
|
400
606
|
{
|
|
401
607
|
id: "search_customers",
|
|
402
608
|
title: "Search customers",
|
|
403
|
-
description: "Find customers by name, email or external id.",
|
|
609
|
+
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.",
|
|
404
610
|
kind: "read",
|
|
405
611
|
scopes: ["customers:read"],
|
|
406
612
|
input: [
|
|
407
613
|
{ name: "q", type: "string", required: false, description: "Free-text search." },
|
|
614
|
+
{
|
|
615
|
+
name: "createdAfter",
|
|
616
|
+
type: "string",
|
|
617
|
+
required: false,
|
|
618
|
+
description: "ISO 8601 lower bound on creation time, exclusive. For a single day, pass that day's midnight and the next day's midnight."
|
|
619
|
+
},
|
|
620
|
+
{
|
|
621
|
+
name: "createdBefore",
|
|
622
|
+
type: "string",
|
|
623
|
+
required: false,
|
|
624
|
+
description: "ISO 8601 upper bound on creation time, exclusive."
|
|
625
|
+
},
|
|
408
626
|
{ name: "limit", type: "number", required: false, description: "Page size." },
|
|
409
|
-
{ name: "cursor", type: "string", required: false, description: "Pagination cursor." }
|
|
627
|
+
{ name: "cursor", type: "string", required: false, description: "Pagination cursor." },
|
|
628
|
+
{
|
|
629
|
+
name: "applicationId",
|
|
630
|
+
type: "string",
|
|
631
|
+
required: false,
|
|
632
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
633
|
+
}
|
|
410
634
|
],
|
|
411
635
|
requests: [{ method: "GET", path: "/api/v1/customers" }],
|
|
412
|
-
channels: ["mcp", "zapier", "make", "n8n"]
|
|
636
|
+
channels: ["mcp", "zapier", "make", "n8n"],
|
|
637
|
+
guardrails: [
|
|
638
|
+
'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.'
|
|
639
|
+
]
|
|
413
640
|
},
|
|
414
641
|
{
|
|
415
642
|
id: "get_customer",
|
|
@@ -417,7 +644,16 @@ var ACTIONS = [
|
|
|
417
644
|
description: "Full picture of a single customer: the record plus their subscriptions and addresses in one response.",
|
|
418
645
|
kind: "read",
|
|
419
646
|
scopes: ["customers:read"],
|
|
420
|
-
|
|
647
|
+
sectionScopes: ["customer_addresses:read", "subscriptions:read"],
|
|
648
|
+
input: [
|
|
649
|
+
{ name: "id", type: "string", required: true, description: "Customer id." },
|
|
650
|
+
{
|
|
651
|
+
name: "applicationId",
|
|
652
|
+
type: "string",
|
|
653
|
+
required: false,
|
|
654
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
655
|
+
}
|
|
656
|
+
],
|
|
421
657
|
requests: [
|
|
422
658
|
{ method: "GET", path: "/api/v1/customers/:id" },
|
|
423
659
|
{ method: "GET", path: "/api/v1/customers/:id/addresses" },
|
|
@@ -425,7 +661,9 @@ var ACTIONS = [
|
|
|
425
661
|
],
|
|
426
662
|
channels: ["mcp"],
|
|
427
663
|
guardrails: [
|
|
428
|
-
"Sub-reads need customer_addresses:read / subscriptions:read. Omit a section the credential cannot read instead of failing."
|
|
664
|
+
"Sub-reads need customer_addresses:read / subscriptions:read. Omit a section the credential cannot read instead of failing.",
|
|
665
|
+
"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.",
|
|
666
|
+
OAUTH_WITHHELD
|
|
429
667
|
]
|
|
430
668
|
},
|
|
431
669
|
// ------------------------------------------------------------- subscriptions
|
|
@@ -438,8 +676,26 @@ var ACTIONS = [
|
|
|
438
676
|
input: [
|
|
439
677
|
{ name: "customerId", type: "string", required: false, description: "Filter by customer." },
|
|
440
678
|
{ name: "status", type: "string", required: false, description: "Filter by status." },
|
|
679
|
+
{
|
|
680
|
+
name: "createdAfter",
|
|
681
|
+
type: "string",
|
|
682
|
+
required: false,
|
|
683
|
+
description: "ISO 8601 lower bound on creation time, exclusive. For a single day, pass that day's midnight and the next day's midnight."
|
|
684
|
+
},
|
|
685
|
+
{
|
|
686
|
+
name: "createdBefore",
|
|
687
|
+
type: "string",
|
|
688
|
+
required: false,
|
|
689
|
+
description: "ISO 8601 upper bound on creation time, exclusive."
|
|
690
|
+
},
|
|
441
691
|
{ name: "limit", type: "number", required: false, description: "Page size." },
|
|
442
|
-
{ name: "cursor", type: "string", required: false, description: "Pagination cursor." }
|
|
692
|
+
{ name: "cursor", type: "string", required: false, description: "Pagination cursor." },
|
|
693
|
+
{
|
|
694
|
+
name: "applicationId",
|
|
695
|
+
type: "string",
|
|
696
|
+
required: false,
|
|
697
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
698
|
+
}
|
|
443
699
|
],
|
|
444
700
|
requests: [{ method: "GET", path: "/api/v1/subscriptions" }],
|
|
445
701
|
channels: ["mcp", "zapier", "make", "n8n"]
|
|
@@ -450,7 +706,15 @@ var ACTIONS = [
|
|
|
450
706
|
description: "Read a single subscription including its current period and pending changes.",
|
|
451
707
|
kind: "read",
|
|
452
708
|
scopes: ["subscriptions:read"],
|
|
453
|
-
input: [
|
|
709
|
+
input: [
|
|
710
|
+
{ name: "id", type: "string", required: true, description: "Subscription id." },
|
|
711
|
+
{
|
|
712
|
+
name: "applicationId",
|
|
713
|
+
type: "string",
|
|
714
|
+
required: false,
|
|
715
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
716
|
+
}
|
|
717
|
+
],
|
|
454
718
|
requests: [{ method: "GET", path: "/api/v1/subscriptions/:id" }],
|
|
455
719
|
channels: ["mcp", "zapier", "make", "n8n"]
|
|
456
720
|
},
|
|
@@ -463,8 +727,26 @@ var ACTIONS = [
|
|
|
463
727
|
scopes: ["quotes:read"],
|
|
464
728
|
input: [
|
|
465
729
|
{ name: "status", type: "string", required: false, description: "Filter by quote status." },
|
|
730
|
+
{
|
|
731
|
+
name: "createdAfter",
|
|
732
|
+
type: "string",
|
|
733
|
+
required: false,
|
|
734
|
+
description: "ISO 8601 lower bound on creation time, exclusive. For a single day, pass that day's midnight and the next day's midnight."
|
|
735
|
+
},
|
|
736
|
+
{
|
|
737
|
+
name: "createdBefore",
|
|
738
|
+
type: "string",
|
|
739
|
+
required: false,
|
|
740
|
+
description: "ISO 8601 upper bound on creation time, exclusive."
|
|
741
|
+
},
|
|
466
742
|
{ name: "limit", type: "number", required: false, description: "Page size." },
|
|
467
|
-
{ name: "cursor", type: "string", required: false, description: "Pagination cursor." }
|
|
743
|
+
{ name: "cursor", type: "string", required: false, description: "Pagination cursor." },
|
|
744
|
+
{
|
|
745
|
+
name: "applicationId",
|
|
746
|
+
type: "string",
|
|
747
|
+
required: false,
|
|
748
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
749
|
+
}
|
|
468
750
|
],
|
|
469
751
|
requests: [{ method: "GET", path: "/api/v1/quotes" }],
|
|
470
752
|
channels: ["mcp", "zapier", "make", "n8n"]
|
|
@@ -472,12 +754,21 @@ var ACTIONS = [
|
|
|
472
754
|
{
|
|
473
755
|
id: "get_quote",
|
|
474
756
|
title: "Get one quote",
|
|
475
|
-
description: "Read a single quote with its line items
|
|
757
|
+
description: "Read a single quote: status, totals, and each revision with its line items and payment terms.",
|
|
476
758
|
kind: "read",
|
|
477
759
|
scopes: ["quotes:read"],
|
|
478
|
-
input: [
|
|
760
|
+
input: [
|
|
761
|
+
{ name: "id", type: "string", required: true, description: "Quote id." },
|
|
762
|
+
{
|
|
763
|
+
name: "applicationId",
|
|
764
|
+
type: "string",
|
|
765
|
+
required: false,
|
|
766
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
767
|
+
}
|
|
768
|
+
],
|
|
479
769
|
requests: [{ method: "GET", path: "/api/v1/quotes/:id" }],
|
|
480
|
-
channels: ["mcp", "zapier", "make", "n8n"]
|
|
770
|
+
channels: ["mcp", "zapier", "make", "n8n"],
|
|
771
|
+
guardrails: [OAUTH_WITHHELD]
|
|
481
772
|
},
|
|
482
773
|
// ----------------------------------------------------------------- invoices
|
|
483
774
|
{
|
|
@@ -489,10 +780,17 @@ var ACTIONS = [
|
|
|
489
780
|
input: [
|
|
490
781
|
{ name: "status", type: "string", required: false, description: "Filter by invoice status." },
|
|
491
782
|
{ name: "limit", type: "number", required: false, description: "Page size." },
|
|
492
|
-
{ name: "cursor", type: "string", required: false, description: "Pagination cursor." }
|
|
783
|
+
{ name: "cursor", type: "string", required: false, description: "Pagination cursor." },
|
|
784
|
+
{
|
|
785
|
+
name: "applicationId",
|
|
786
|
+
type: "string",
|
|
787
|
+
required: false,
|
|
788
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
789
|
+
}
|
|
493
790
|
],
|
|
494
791
|
requests: [{ method: "GET", path: "/api/v1/invoices" }],
|
|
495
|
-
channels: ["mcp", "zapier", "make", "n8n"]
|
|
792
|
+
channels: ["mcp", "zapier", "make", "n8n"],
|
|
793
|
+
guardrails: [OAUTH_WITHHELD]
|
|
496
794
|
},
|
|
497
795
|
{
|
|
498
796
|
id: "receivables_aging",
|
|
@@ -500,10 +798,170 @@ var ACTIONS = [
|
|
|
500
798
|
description: 'Outstanding receivables bucketed by age \u2014 answers "what is overdue and by how long".',
|
|
501
799
|
kind: "read",
|
|
502
800
|
scopes: ["invoices:read"],
|
|
503
|
-
input: [
|
|
801
|
+
input: [
|
|
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
|
+
],
|
|
504
809
|
requests: [{ method: "GET", path: "/api/v1/invoices/aging" }],
|
|
505
810
|
channels: ["mcp", "zapier", "make", "n8n"]
|
|
506
811
|
},
|
|
812
|
+
// --------------------------------------------------------------- discounts
|
|
813
|
+
{
|
|
814
|
+
id: "search_discounts",
|
|
815
|
+
title: "Search discounts",
|
|
816
|
+
description: "List discount codes with optional filters \u2014 type, active state, and creation date range.",
|
|
817
|
+
kind: "read",
|
|
818
|
+
scopes: ["discounts:read"],
|
|
819
|
+
input: [
|
|
820
|
+
{ name: "q", type: "string", required: false, description: "Free-text search." },
|
|
821
|
+
{ name: "code", type: "string", required: false, description: "Exact code match." },
|
|
822
|
+
{
|
|
823
|
+
name: "type",
|
|
824
|
+
type: "string",
|
|
825
|
+
required: false,
|
|
826
|
+
description: "Filter by discount type.",
|
|
827
|
+
enum: ["percentage", "fixed_amount"]
|
|
828
|
+
},
|
|
829
|
+
{ name: "isActive", type: "boolean", required: false, description: "Filter by active state." },
|
|
830
|
+
{ name: "createdAfter", type: "string", required: false, description: "ISO 8601 date lower bound." },
|
|
831
|
+
{ name: "createdBefore", type: "string", required: false, description: "ISO 8601 date upper bound." },
|
|
832
|
+
{ name: "limit", type: "number", required: false, description: "Page size." },
|
|
833
|
+
{ name: "cursor", type: "string", required: false, description: "Pagination cursor." },
|
|
834
|
+
{
|
|
835
|
+
name: "applicationId",
|
|
836
|
+
type: "string",
|
|
837
|
+
required: false,
|
|
838
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
839
|
+
}
|
|
840
|
+
],
|
|
841
|
+
requests: [{ method: "GET", path: "/api/v1/discounts" }],
|
|
842
|
+
channels: ["mcp", "zapier", "make", "n8n"]
|
|
843
|
+
},
|
|
844
|
+
{
|
|
845
|
+
id: "get_discount",
|
|
846
|
+
title: "Get one discount",
|
|
847
|
+
description: "Read a single discount code: value, conditions, usage limits and redemption count.",
|
|
848
|
+
kind: "read",
|
|
849
|
+
scopes: ["discounts:read"],
|
|
850
|
+
input: [
|
|
851
|
+
{ name: "id", type: "string", required: true, description: "Discount id." },
|
|
852
|
+
{
|
|
853
|
+
name: "applicationId",
|
|
854
|
+
type: "string",
|
|
855
|
+
required: false,
|
|
856
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
857
|
+
}
|
|
858
|
+
],
|
|
859
|
+
requests: [{ method: "GET", path: "/api/v1/discounts/:id" }],
|
|
860
|
+
channels: ["mcp", "zapier", "make", "n8n"]
|
|
861
|
+
},
|
|
862
|
+
// ---------------------------------------------------------------- payments
|
|
863
|
+
{
|
|
864
|
+
id: "search_payments",
|
|
865
|
+
title: "Search payments",
|
|
866
|
+
description: "List payments with optional filters \u2014 method, status, order, amount range, or a Net-N overdue bucket. Today payments are only reachable nested inside get_order; this is the standalone entry point.",
|
|
867
|
+
kind: "read",
|
|
868
|
+
scopes: ["payments:read"],
|
|
869
|
+
input: [
|
|
870
|
+
{ name: "method", type: "string", required: false, description: "Filter by payment method." },
|
|
871
|
+
{ name: "status", type: "string", required: false, description: "Filter by payment status." },
|
|
872
|
+
{ name: "orderId", type: "string", required: false, description: "Filter to one order." },
|
|
873
|
+
{
|
|
874
|
+
name: "aging",
|
|
875
|
+
type: "string",
|
|
876
|
+
required: false,
|
|
877
|
+
description: "Overdue bucket filter, Net-N payments only.",
|
|
878
|
+
enum: ["0_30", "31_60", "61_90", "90_plus"]
|
|
879
|
+
},
|
|
880
|
+
{ name: "dueFrom", type: "string", required: false, description: "Due-date range start, YYYY-MM-DD." },
|
|
881
|
+
{ name: "dueTo", type: "string", required: false, description: "Due-date range end, YYYY-MM-DD." },
|
|
882
|
+
{ name: "amountMin", type: "number", required: false, description: "Minimum amount, minor units." },
|
|
883
|
+
{ name: "amountMax", type: "number", required: false, description: "Maximum amount, minor units." },
|
|
884
|
+
{ name: "limit", type: "number", required: false, description: "Page size." },
|
|
885
|
+
{ name: "cursor", type: "string", required: false, description: "Pagination cursor." },
|
|
886
|
+
{
|
|
887
|
+
name: "applicationId",
|
|
888
|
+
type: "string",
|
|
889
|
+
required: false,
|
|
890
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
891
|
+
}
|
|
892
|
+
],
|
|
893
|
+
requests: [{ method: "GET", path: "/api/v1/payments" }],
|
|
894
|
+
channels: ["mcp", "zapier", "make", "n8n"]
|
|
895
|
+
},
|
|
896
|
+
{
|
|
897
|
+
id: "get_payment",
|
|
898
|
+
title: "Get one payment with its transactions",
|
|
899
|
+
description: "Read a single payment plus its full transaction history (authorize/capture/refund/void attempts).",
|
|
900
|
+
kind: "read",
|
|
901
|
+
scopes: ["payments:read"],
|
|
902
|
+
input: [
|
|
903
|
+
{ name: "id", type: "string", required: true, description: "Payment id." },
|
|
904
|
+
{
|
|
905
|
+
name: "applicationId",
|
|
906
|
+
type: "string",
|
|
907
|
+
required: false,
|
|
908
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
909
|
+
}
|
|
910
|
+
],
|
|
911
|
+
requests: [
|
|
912
|
+
{ method: "GET", path: "/api/v1/payments/:id" },
|
|
913
|
+
{ method: "GET", path: "/api/v1/payments/:id/transactions" }
|
|
914
|
+
],
|
|
915
|
+
channels: ["mcp"],
|
|
916
|
+
guardrails: [
|
|
917
|
+
"The transactions sub-read needs its own read of the same payment scope; report it as unavailable rather than failing the whole call if the underlying request errors."
|
|
918
|
+
]
|
|
919
|
+
},
|
|
920
|
+
// ---------------------------------------------------------------- invoices
|
|
921
|
+
{
|
|
922
|
+
id: "get_invoice",
|
|
923
|
+
title: "Get one invoice",
|
|
924
|
+
description: "Read a single issued invoice: line items, totals, status and the billing snapshot at issue time.",
|
|
925
|
+
kind: "read",
|
|
926
|
+
scopes: ["invoices:read"],
|
|
927
|
+
input: [
|
|
928
|
+
{ name: "id", type: "string", required: true, description: "Invoice id." },
|
|
929
|
+
{
|
|
930
|
+
name: "applicationId",
|
|
931
|
+
type: "string",
|
|
932
|
+
required: false,
|
|
933
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
934
|
+
}
|
|
935
|
+
],
|
|
936
|
+
requests: [{ method: "GET", path: "/api/v1/invoices/:id" }],
|
|
937
|
+
channels: ["mcp", "zapier", "make", "n8n"],
|
|
938
|
+
guardrails: [OAUTH_WITHHELD]
|
|
939
|
+
},
|
|
940
|
+
// ------------------------------------------------------------------ carts
|
|
941
|
+
{
|
|
942
|
+
id: "list_abandoned_carts",
|
|
943
|
+
title: "List abandoned carts",
|
|
944
|
+
description: "Carts that were started but never finalized, with recovery status \u2014 the abandoned-cart recovery list.",
|
|
945
|
+
kind: "read",
|
|
946
|
+
scopes: ["carts:read"],
|
|
947
|
+
input: [
|
|
948
|
+
{ name: "sinceDays", type: "number", required: false, description: "How many days back to look." },
|
|
949
|
+
{ name: "limit", type: "number", required: false, description: "Page size." },
|
|
950
|
+
{ name: "cursor", type: "string", required: false, description: "Pagination cursor." },
|
|
951
|
+
{
|
|
952
|
+
name: "applicationId",
|
|
953
|
+
type: "string",
|
|
954
|
+
required: false,
|
|
955
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
956
|
+
}
|
|
957
|
+
],
|
|
958
|
+
requests: [
|
|
959
|
+
{ method: "GET", path: "/api/v1/abandoned-carts" },
|
|
960
|
+
{ method: "GET", path: "/api/v1/abandoned-carts/summary" }
|
|
961
|
+
],
|
|
962
|
+
channels: ["mcp", "zapier", "make", "n8n"],
|
|
963
|
+
guardrails: ["Report the summary as unavailable rather than failing the whole call if that sub-read errors."]
|
|
964
|
+
},
|
|
507
965
|
// ---------------------------------------------------------------- debugging
|
|
508
966
|
{
|
|
509
967
|
id: "search_events",
|
|
@@ -516,7 +974,13 @@ var ACTIONS = [
|
|
|
516
974
|
{ name: "entityType", type: "string", required: false, description: "Entity type filter." },
|
|
517
975
|
{ name: "entityId", type: "string", required: false, description: "Entity id filter." },
|
|
518
976
|
{ name: "limit", type: "number", required: false, description: "Page size." },
|
|
519
|
-
{ name: "cursor", type: "string", required: false, description: "Pagination cursor." }
|
|
977
|
+
{ name: "cursor", type: "string", required: false, description: "Pagination cursor." },
|
|
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
|
+
}
|
|
520
984
|
],
|
|
521
985
|
requests: [{ method: "GET", path: "/api/v1/events" }],
|
|
522
986
|
channels: ["mcp", "n8n"]
|
|
@@ -524,13 +988,30 @@ var ACTIONS = [
|
|
|
524
988
|
{
|
|
525
989
|
id: "list_webhook_deliveries",
|
|
526
990
|
title: "List webhook deliveries",
|
|
527
|
-
description: "Recent outbound webhook deliveries, filterable by endpoint and
|
|
991
|
+
description: "Recent outbound webhook deliveries, filterable by endpoint, status and event type.",
|
|
528
992
|
kind: "read",
|
|
529
|
-
scopes: ["
|
|
993
|
+
scopes: ["webhook_deliveries:read"],
|
|
530
994
|
input: [
|
|
531
995
|
{ name: "endpointId", type: "string", required: false, description: "Filter by endpoint." },
|
|
532
|
-
{
|
|
533
|
-
|
|
996
|
+
{
|
|
997
|
+
name: "status",
|
|
998
|
+
type: "string",
|
|
999
|
+
required: false,
|
|
1000
|
+
description: "Filter by delivery status: pending, delivered, failed or dead_letter."
|
|
1001
|
+
},
|
|
1002
|
+
{
|
|
1003
|
+
name: "eventType",
|
|
1004
|
+
type: "string",
|
|
1005
|
+
required: false,
|
|
1006
|
+
description: "Filter by exact event type, e.g. payment.failed."
|
|
1007
|
+
},
|
|
1008
|
+
{ name: "limit", type: "number", required: false, description: "Page size." },
|
|
1009
|
+
{
|
|
1010
|
+
name: "applicationId",
|
|
1011
|
+
type: "string",
|
|
1012
|
+
required: false,
|
|
1013
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
1014
|
+
}
|
|
534
1015
|
],
|
|
535
1016
|
requests: [{ method: "GET", path: "/api/v1/webhook-deliveries" }],
|
|
536
1017
|
channels: ["mcp", "n8n"]
|
|
@@ -540,8 +1021,16 @@ var ACTIONS = [
|
|
|
540
1021
|
title: "Get one webhook delivery",
|
|
541
1022
|
description: "Full delivery record: the signed payload we sent, the response we got back, and every attempt.",
|
|
542
1023
|
kind: "read",
|
|
543
|
-
scopes: ["
|
|
544
|
-
input: [
|
|
1024
|
+
scopes: ["webhook_deliveries:read"],
|
|
1025
|
+
input: [
|
|
1026
|
+
{ name: "id", type: "string", required: true, description: "Delivery id." },
|
|
1027
|
+
{
|
|
1028
|
+
name: "applicationId",
|
|
1029
|
+
type: "string",
|
|
1030
|
+
required: false,
|
|
1031
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
1032
|
+
}
|
|
1033
|
+
],
|
|
545
1034
|
requests: [{ method: "GET", path: "/api/v1/webhook-deliveries/:id" }],
|
|
546
1035
|
channels: ["mcp", "n8n"]
|
|
547
1036
|
},
|
|
@@ -551,12 +1040,19 @@ var ACTIONS = [
|
|
|
551
1040
|
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.",
|
|
552
1041
|
kind: "read",
|
|
553
1042
|
scopes: ["checkout_sessions:read"],
|
|
1043
|
+
sectionScopes: ["events:read", "webhook_deliveries:read"],
|
|
554
1044
|
input: [
|
|
555
1045
|
{
|
|
556
1046
|
name: "id",
|
|
557
1047
|
type: "string",
|
|
558
1048
|
required: true,
|
|
559
1049
|
description: "Checkout session id (or cart id \u2014 the tool resolves the session)."
|
|
1050
|
+
},
|
|
1051
|
+
{
|
|
1052
|
+
name: "applicationId",
|
|
1053
|
+
type: "string",
|
|
1054
|
+
required: false,
|
|
1055
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
560
1056
|
}
|
|
561
1057
|
],
|
|
562
1058
|
requests: [
|
|
@@ -567,7 +1063,61 @@ var ACTIONS = [
|
|
|
567
1063
|
],
|
|
568
1064
|
channels: ["mcp"],
|
|
569
1065
|
guardrails: [
|
|
570
|
-
"Report which sections were unavailable rather than failing the call when a sub-read is unscoped."
|
|
1066
|
+
"Report which sections were unavailable rather than failing the call when a sub-read is unscoped.",
|
|
1067
|
+
OAUTH_WITHHELD
|
|
1068
|
+
]
|
|
1069
|
+
},
|
|
1070
|
+
// ------------------------------------------------------------- extensions
|
|
1071
|
+
{
|
|
1072
|
+
id: "list_extension_installations",
|
|
1073
|
+
title: "List installed extensions",
|
|
1074
|
+
description: "Extensions installed on this application, with status and configuration.",
|
|
1075
|
+
kind: "read",
|
|
1076
|
+
scopes: ["extensions:read"],
|
|
1077
|
+
input: [
|
|
1078
|
+
{
|
|
1079
|
+
name: "status",
|
|
1080
|
+
type: "string",
|
|
1081
|
+
required: false,
|
|
1082
|
+
description: "Filter by installation status.",
|
|
1083
|
+
enum: ["active", "suspended", "uninstalled"]
|
|
1084
|
+
},
|
|
1085
|
+
{ name: "limit", type: "number", required: false, description: "Page size." },
|
|
1086
|
+
{ name: "cursor", type: "string", required: false, description: "Pagination cursor." },
|
|
1087
|
+
{
|
|
1088
|
+
name: "applicationId",
|
|
1089
|
+
type: "string",
|
|
1090
|
+
required: false,
|
|
1091
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
1092
|
+
}
|
|
1093
|
+
],
|
|
1094
|
+
requests: [{ method: "GET", path: "/api/v1/installations" }],
|
|
1095
|
+
channels: ["mcp", "zapier", "make", "n8n"]
|
|
1096
|
+
},
|
|
1097
|
+
// ------------------------------------------------------------- connectors
|
|
1098
|
+
{
|
|
1099
|
+
id: "get_connector_routing",
|
|
1100
|
+
title: "Payment routing and decline rules",
|
|
1101
|
+
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.",
|
|
1102
|
+
kind: "read",
|
|
1103
|
+
scopes: ["connectors:read"],
|
|
1104
|
+
input: [
|
|
1105
|
+
{
|
|
1106
|
+
name: "applicationId",
|
|
1107
|
+
type: "string",
|
|
1108
|
+
required: false,
|
|
1109
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
1110
|
+
}
|
|
1111
|
+
],
|
|
1112
|
+
requests: [
|
|
1113
|
+
{ method: "GET", path: "/api/v1/payment-routing" },
|
|
1114
|
+
{ method: "GET", path: "/api/v1/routing-connectors" },
|
|
1115
|
+
{ method: "GET", path: "/api/v1/card-decline" },
|
|
1116
|
+
{ method: "GET", path: "/api/v1/other-decline" }
|
|
1117
|
+
],
|
|
1118
|
+
channels: ["mcp"],
|
|
1119
|
+
guardrails: [
|
|
1120
|
+
"Every sub-read proxies live to the payment provider's routing API and none are guaranteed to succeed together; report any that fail as unavailable rather than failing the whole call."
|
|
571
1121
|
]
|
|
572
1122
|
},
|
|
573
1123
|
{
|
|
@@ -576,7 +1126,15 @@ var ACTIONS = [
|
|
|
576
1126
|
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.',
|
|
577
1127
|
kind: "read",
|
|
578
1128
|
scopes: [],
|
|
579
|
-
|
|
1129
|
+
sectionScopes: ["connectors:read", "webhooks:read"],
|
|
1130
|
+
input: [
|
|
1131
|
+
{
|
|
1132
|
+
name: "applicationId",
|
|
1133
|
+
type: "string",
|
|
1134
|
+
required: false,
|
|
1135
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
1136
|
+
}
|
|
1137
|
+
],
|
|
580
1138
|
requests: [
|
|
581
1139
|
{ method: "GET", path: "/api/v1/whoami" },
|
|
582
1140
|
{ method: "GET", path: "/api/v1/connectors" },
|
|
@@ -588,39 +1146,277 @@ var ACTIONS = [
|
|
|
588
1146
|
'Each section needs its own scope (connectors:read, webhooks:read). Report a section as "not readable with this credential" instead of failing.'
|
|
589
1147
|
]
|
|
590
1148
|
},
|
|
1149
|
+
// --------------------------------------------------------------- workspace
|
|
1150
|
+
{
|
|
1151
|
+
id: "get_workspace_settings",
|
|
1152
|
+
title: "Get workspace settings",
|
|
1153
|
+
description: "Read this workspace's identity: name, slug, branding and onboarding state. Payment terms are set per application and environment, not here.",
|
|
1154
|
+
kind: "read",
|
|
1155
|
+
scopes: ["merchant:read"],
|
|
1156
|
+
input: [],
|
|
1157
|
+
requests: [{ method: "GET", path: "/api/v1/merchants/me" }],
|
|
1158
|
+
channels: ["mcp", "zapier", "make", "n8n"]
|
|
1159
|
+
},
|
|
1160
|
+
{
|
|
1161
|
+
id: "get_audit_log",
|
|
1162
|
+
title: "Get the workspace audit log",
|
|
1163
|
+
description: "Recent admin actions taken on this workspace \u2014 invites, billing changes, key rotation, suspensions.",
|
|
1164
|
+
kind: "read",
|
|
1165
|
+
scopes: ["audit_logs:read"],
|
|
1166
|
+
input: [
|
|
1167
|
+
{ name: "limit", type: "number", required: false, description: "Maximum entries (1-200).", default: 100 }
|
|
1168
|
+
],
|
|
1169
|
+
requests: [{ method: "GET", path: "/api/v1/audit-log" }],
|
|
1170
|
+
channels: ["mcp", "zapier", "make", "n8n"]
|
|
1171
|
+
},
|
|
1172
|
+
{
|
|
1173
|
+
id: "list_applications",
|
|
1174
|
+
title: "List applications in this workspace",
|
|
1175
|
+
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.',
|
|
1176
|
+
kind: "read",
|
|
1177
|
+
scopes: ["applications:read"],
|
|
1178
|
+
input: [],
|
|
1179
|
+
requests: [{ method: "GET", path: "/api/v1/stores" }],
|
|
1180
|
+
channels: ["mcp", "zapier", "make", "n8n"]
|
|
1181
|
+
},
|
|
1182
|
+
{
|
|
1183
|
+
id: "get_sales_timeseries",
|
|
1184
|
+
title: "Sales over time",
|
|
1185
|
+
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.`,
|
|
1186
|
+
kind: "read",
|
|
1187
|
+
scopes: ["orders:read"],
|
|
1188
|
+
input: [
|
|
1189
|
+
{ name: "from", type: "string", required: false, description: 'ISO 8601 start of range, exclusive. Defaults to 30 days before "to".' },
|
|
1190
|
+
{ name: "to", type: "string", required: false, description: "ISO 8601 end of range, exclusive. Defaults to now." },
|
|
1191
|
+
{ name: "groupBy", type: "string", required: false, description: "Bucket size.", enum: ["day", "week", "month"], default: "day" },
|
|
1192
|
+
{ 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." },
|
|
1193
|
+
{ name: "compareTo", type: "string", required: false, description: "Add a comparison total.", enum: ["previous_period", "previous_year", "none"], default: "none" },
|
|
1194
|
+
{
|
|
1195
|
+
name: "applicationId",
|
|
1196
|
+
type: "string",
|
|
1197
|
+
required: false,
|
|
1198
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
1199
|
+
}
|
|
1200
|
+
],
|
|
1201
|
+
requests: [{ method: "GET", path: "/api/v1/analytics/orders-timeseries" }],
|
|
1202
|
+
channels: ["mcp", "zapier", "make", "n8n"],
|
|
1203
|
+
guardrails: [
|
|
1204
|
+
"Revenue is GROSS. Refunds are not deducted, because that needs payment_refunds:read, which OAuth connections cannot hold. Never describe the figure as net."
|
|
1205
|
+
]
|
|
1206
|
+
},
|
|
591
1207
|
{
|
|
592
1208
|
id: "get_business_summary",
|
|
593
1209
|
title: "Business summary",
|
|
594
|
-
description: "How are we doing? Orders/revenue, subscription MRR, receivables and quotes pipeline
|
|
1210
|
+
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.",
|
|
1211
|
+
kind: "read",
|
|
1212
|
+
scopes: [],
|
|
1213
|
+
sectionScopes: ["orders:read", "subscriptions:read", "invoices:read", "quotes:read"],
|
|
1214
|
+
input: [
|
|
1215
|
+
{
|
|
1216
|
+
name: "period",
|
|
1217
|
+
type: "string",
|
|
1218
|
+
required: false,
|
|
1219
|
+
description: "Reporting window.",
|
|
1220
|
+
enum: ["week", "month", "quarter"],
|
|
1221
|
+
default: "month"
|
|
1222
|
+
},
|
|
1223
|
+
{ name: "from", type: "string", required: false, description: "ISO 8601 start of range. Overrides period." },
|
|
1224
|
+
{ name: "to", type: "string", required: false, description: "ISO 8601 end of range. Defaults to now." },
|
|
1225
|
+
{ name: "timezone", type: "string", required: false, description: "IANA timezone for period boundaries. Defaults to UTC." },
|
|
1226
|
+
{
|
|
1227
|
+
name: "applicationId",
|
|
1228
|
+
type: "string",
|
|
1229
|
+
required: false,
|
|
1230
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
1231
|
+
}
|
|
1232
|
+
],
|
|
1233
|
+
requests: [{ method: "GET", path: "/api/v1/analytics/business-summary" }],
|
|
1234
|
+
channels: ["mcp", "zapier", "make", "n8n"],
|
|
1235
|
+
guardrails: [
|
|
1236
|
+
"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."
|
|
1237
|
+
]
|
|
1238
|
+
},
|
|
1239
|
+
{
|
|
1240
|
+
id: "get_top_products",
|
|
1241
|
+
title: "Top products",
|
|
1242
|
+
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.",
|
|
1243
|
+
kind: "read",
|
|
1244
|
+
scopes: ["orders:read"],
|
|
1245
|
+
input: [
|
|
1246
|
+
{ name: "from", type: "string", required: false, description: 'ISO 8601 start of range, exclusive. Defaults to 30 days before "to".' },
|
|
1247
|
+
{ name: "to", type: "string", required: false, description: "ISO 8601 end of range, exclusive. Defaults to now." },
|
|
1248
|
+
{ name: "timezone", type: "string", required: false, description: "IANA timezone for range boundaries. Defaults to UTC." },
|
|
1249
|
+
{ name: "by", type: "string", required: false, description: "Sort key.", enum: ["revenue", "units"], default: "revenue" },
|
|
1250
|
+
{ name: "limit", type: "number", required: false, description: "Max rows to return (1-100). Defaults to 20." },
|
|
1251
|
+
{
|
|
1252
|
+
name: "applicationId",
|
|
1253
|
+
type: "string",
|
|
1254
|
+
required: false,
|
|
1255
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
1256
|
+
}
|
|
1257
|
+
],
|
|
1258
|
+
requests: [{ method: "GET", path: "/api/v1/analytics/products" }],
|
|
1259
|
+
channels: ["mcp", "zapier", "make", "n8n"],
|
|
1260
|
+
guardrails: [
|
|
1261
|
+
"Revenue is GROSS. Refunds are not deducted, because that needs payment_refunds:read, which OAuth connections cannot hold. Never describe the figure as net."
|
|
1262
|
+
]
|
|
1263
|
+
},
|
|
1264
|
+
{
|
|
1265
|
+
id: "get_attention_digest",
|
|
1266
|
+
title: "Attention digest",
|
|
1267
|
+
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.",
|
|
1268
|
+
kind: "read",
|
|
1269
|
+
scopes: [],
|
|
1270
|
+
sectionScopes: ["payment_disputes:read", "subscriptions:read", "invoices:read", "webhook_deliveries:read"],
|
|
1271
|
+
input: [
|
|
1272
|
+
{
|
|
1273
|
+
name: "applicationId",
|
|
1274
|
+
type: "string",
|
|
1275
|
+
required: false,
|
|
1276
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
1277
|
+
}
|
|
1278
|
+
],
|
|
1279
|
+
requests: [{ method: "GET", path: "/api/v1/analytics/attention-digest" }],
|
|
1280
|
+
channels: ["mcp", "zapier", "make", "n8n"],
|
|
1281
|
+
guardrails: [
|
|
1282
|
+
"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."
|
|
1283
|
+
]
|
|
1284
|
+
},
|
|
1285
|
+
{
|
|
1286
|
+
id: "get_customer_growth",
|
|
1287
|
+
title: "Customer growth",
|
|
1288
|
+
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.",
|
|
1289
|
+
kind: "read",
|
|
1290
|
+
scopes: ["customers:read"],
|
|
1291
|
+
input: [
|
|
1292
|
+
{ name: "from", type: "string", required: false, description: 'ISO 8601 start of range, exclusive. Defaults to 30 days before "to".' },
|
|
1293
|
+
{ name: "to", type: "string", required: false, description: "ISO 8601 end of range, exclusive. Defaults to now." },
|
|
1294
|
+
{ name: "groupBy", type: "string", required: false, description: "Signup series bucket size.", enum: ["day", "week", "month"], default: "day" },
|
|
1295
|
+
{ name: "timezone", type: "string", required: false, description: "IANA timezone for bucket boundaries. Defaults to UTC." },
|
|
1296
|
+
{ name: "topBy", type: "string", required: false, description: "Sort key for top customers.", enum: ["revenue", "orders"], default: "revenue" },
|
|
1297
|
+
{ name: "topLimit", type: "number", required: false, description: "Max top-customers rows to return (1-50). Defaults to 10." },
|
|
1298
|
+
{
|
|
1299
|
+
name: "applicationId",
|
|
1300
|
+
type: "string",
|
|
1301
|
+
required: false,
|
|
1302
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
1303
|
+
}
|
|
1304
|
+
],
|
|
1305
|
+
requests: [{ method: "GET", path: "/api/v1/analytics/customers" }],
|
|
1306
|
+
channels: ["mcp", "zapier", "make", "n8n"],
|
|
1307
|
+
guardrails: [
|
|
1308
|
+
"Revenue is GROSS. Refunds are not deducted, because that needs payment_refunds:read, which OAuth connections cannot hold. Never describe the figure as net.",
|
|
1309
|
+
"topCustomers is a top-N list; use the accompanying totals for range-wide customer/order/revenue counts instead of summing the returned rows.",
|
|
1310
|
+
"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."
|
|
1311
|
+
]
|
|
1312
|
+
},
|
|
1313
|
+
{
|
|
1314
|
+
id: "get_revenue_breakdown",
|
|
1315
|
+
title: "Revenue breakdown",
|
|
1316
|
+
description: "Revenue split by one dimension over a date range \u2014 payment method, order source, or currency. Amounts are gross; refunds are not deducted.",
|
|
1317
|
+
kind: "read",
|
|
1318
|
+
scopes: ["orders:read"],
|
|
1319
|
+
input: [
|
|
1320
|
+
{
|
|
1321
|
+
name: "dimension",
|
|
1322
|
+
type: "string",
|
|
1323
|
+
required: true,
|
|
1324
|
+
description: "Breakdown dimension.",
|
|
1325
|
+
enum: ["paymentMethod", "source", "currency"]
|
|
1326
|
+
},
|
|
1327
|
+
{ name: "from", type: "string", required: false, description: 'ISO 8601 start of range, exclusive. Defaults to 30 days before "to".' },
|
|
1328
|
+
{ name: "to", type: "string", required: false, description: "ISO 8601 end of range, exclusive. Defaults to now." },
|
|
1329
|
+
{ name: "timezone", type: "string", required: false, description: "IANA timezone for range boundaries. Defaults to UTC." },
|
|
1330
|
+
{
|
|
1331
|
+
name: "applicationId",
|
|
1332
|
+
type: "string",
|
|
1333
|
+
required: false,
|
|
1334
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
1335
|
+
}
|
|
1336
|
+
],
|
|
1337
|
+
requests: [{ method: "GET", path: "/api/v1/analytics/revenue-breakdown" }],
|
|
1338
|
+
channels: ["mcp", "zapier", "make", "n8n"],
|
|
1339
|
+
guardrails: [
|
|
1340
|
+
"Revenue is GROSS. Refunds are not deducted, because that needs payment_refunds:read, which OAuth connections cannot hold. Never describe the figure as net.",
|
|
1341
|
+
"dimension is required; it is never defaulted, so omitting it fails the call instead of silently picking a dimension.",
|
|
1342
|
+
"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)."
|
|
1343
|
+
]
|
|
1344
|
+
},
|
|
1345
|
+
{
|
|
1346
|
+
id: "get_funnel_conversion",
|
|
1347
|
+
title: "Checkout funnel",
|
|
1348
|
+
description: "Checkout funnel over a date range: carts created, checkout sessions started, orders completed, with the drop-off between each stage.",
|
|
1349
|
+
kind: "read",
|
|
1350
|
+
scopes: ["carts:read"],
|
|
1351
|
+
input: [
|
|
1352
|
+
{ name: "from", type: "string", required: false, description: 'ISO 8601 start of range, exclusive. Defaults to 30 days before "to".' },
|
|
1353
|
+
{ name: "to", type: "string", required: false, description: "ISO 8601 end of range, exclusive. Defaults to now." },
|
|
1354
|
+
{ name: "timezone", type: "string", required: false, description: "IANA timezone for range boundaries. Defaults to UTC." },
|
|
1355
|
+
{
|
|
1356
|
+
name: "applicationId",
|
|
1357
|
+
type: "string",
|
|
1358
|
+
required: false,
|
|
1359
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
1360
|
+
}
|
|
1361
|
+
],
|
|
1362
|
+
requests: [{ method: "GET", path: "/api/v1/analytics/funnel" }],
|
|
1363
|
+
channels: ["mcp", "zapier", "make", "n8n"],
|
|
1364
|
+
guardrails: [
|
|
1365
|
+
"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.",
|
|
1366
|
+
"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.",
|
|
1367
|
+
'dropOff[].ratePct is the percentage of "from" that did NOT reach "to" (loss), not a conversion rate. The step-conversion rate is 100 - ratePct.'
|
|
1368
|
+
]
|
|
1369
|
+
},
|
|
1370
|
+
{
|
|
1371
|
+
id: "get_subscription_movement",
|
|
1372
|
+
title: "Subscription movement",
|
|
1373
|
+
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.",
|
|
595
1374
|
kind: "read",
|
|
596
|
-
scopes: [],
|
|
1375
|
+
scopes: ["subscriptions:read"],
|
|
597
1376
|
input: [
|
|
1377
|
+
{ name: "from", type: "string", required: false, description: 'ISO 8601 start of range, exclusive. Defaults to 30 days before "to".' },
|
|
1378
|
+
{ name: "to", type: "string", required: false, description: "ISO 8601 end of range, exclusive. Defaults to now." },
|
|
1379
|
+
{ name: "timezone", type: "string", required: false, description: "IANA timezone for bucket boundaries. Defaults to UTC." },
|
|
1380
|
+
{ name: "groupBy", type: "string", required: false, description: "Bucket size.", enum: ["day", "week", "month"], default: "day" },
|
|
598
1381
|
{
|
|
599
|
-
name: "
|
|
1382
|
+
name: "applicationId",
|
|
600
1383
|
type: "string",
|
|
601
1384
|
required: false,
|
|
602
|
-
description: "
|
|
603
|
-
enum: ["week", "month", "quarter"],
|
|
604
|
-
default: "month"
|
|
1385
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
605
1386
|
}
|
|
606
1387
|
],
|
|
607
|
-
requests: [{ method: "GET", path: "/api/v1/analytics/
|
|
1388
|
+
requests: [{ method: "GET", path: "/api/v1/analytics/subscriptions" }],
|
|
608
1389
|
channels: ["mcp", "zapier", "make", "n8n"],
|
|
609
1390
|
guardrails: [
|
|
610
|
-
"
|
|
1391
|
+
"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.",
|
|
1392
|
+
"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.",
|
|
1393
|
+
"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[]."
|
|
611
1394
|
]
|
|
612
1395
|
},
|
|
613
1396
|
{
|
|
614
|
-
id: "
|
|
615
|
-
title: "
|
|
616
|
-
description: "
|
|
1397
|
+
id: "get_discount_performance",
|
|
1398
|
+
title: "Discount performance",
|
|
1399
|
+
description: "Discount code performance over a date range: redemption count, revenue on discounted orders, and total discount given per code.",
|
|
617
1400
|
kind: "read",
|
|
618
|
-
scopes: [],
|
|
619
|
-
input: [
|
|
620
|
-
|
|
1401
|
+
scopes: ["discounts:read"],
|
|
1402
|
+
input: [
|
|
1403
|
+
{ name: "from", type: "string", required: false, description: 'ISO 8601 start of range, exclusive. Defaults to 30 days before "to".' },
|
|
1404
|
+
{ name: "to", type: "string", required: false, description: "ISO 8601 end of range, exclusive. Defaults to now." },
|
|
1405
|
+
{ name: "timezone", type: "string", required: false, description: "IANA timezone for range boundaries. Defaults to UTC." },
|
|
1406
|
+
{ name: "limit", type: "number", required: false, description: "Max rows to return. Defaults to 20." },
|
|
1407
|
+
{
|
|
1408
|
+
name: "applicationId",
|
|
1409
|
+
type: "string",
|
|
1410
|
+
required: false,
|
|
1411
|
+
description: "Application to read from. Defaults to the one this connection was authorized for. Call list_applications to see the others."
|
|
1412
|
+
}
|
|
1413
|
+
],
|
|
1414
|
+
requests: [{ method: "GET", path: "/api/v1/analytics/discounts" }],
|
|
621
1415
|
channels: ["mcp", "zapier", "make", "n8n"],
|
|
622
1416
|
guardrails: [
|
|
623
|
-
|
|
1417
|
+
'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.',
|
|
1418
|
+
"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.",
|
|
1419
|
+
"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."
|
|
624
1420
|
]
|
|
625
1421
|
},
|
|
626
1422
|
// ------------------------------------------------------------ sandbox write
|
|
@@ -648,6 +1444,151 @@ var ACTIONS = [
|
|
|
648
1444
|
"Seeds a finalized DRAFT order \u2014 it does not capture a payment, which needs a browser-side tokenised payment method."
|
|
649
1445
|
]
|
|
650
1446
|
},
|
|
1447
|
+
// ---------------------------------------------------------- inbound writes
|
|
1448
|
+
// The no-code channels are mostly used in the "something happened elsewhere,
|
|
1449
|
+
// put it into Throttle" direction — a form submission, a new CRM contact, a
|
|
1450
|
+
// spreadsheet row. These three are that direction.
|
|
1451
|
+
{
|
|
1452
|
+
id: "create_customer",
|
|
1453
|
+
title: "Create a customer",
|
|
1454
|
+
description: "Create a customer record. Use search_customers first if the buyer may already exist \u2014 this does not de-duplicate by email.",
|
|
1455
|
+
kind: "write",
|
|
1456
|
+
scopes: ["customers:write"],
|
|
1457
|
+
input: [
|
|
1458
|
+
{ name: "email", type: "string", required: true, description: "The buyer's email address." },
|
|
1459
|
+
{ name: "firstName", type: "string", required: false, description: "Given name." },
|
|
1460
|
+
{ name: "lastName", type: "string", required: false, description: "Family name." },
|
|
1461
|
+
{ name: "phone", type: "string", required: false, description: "Contact phone number." },
|
|
1462
|
+
{ name: "company", type: "string", required: false, description: "The buyer's company." },
|
|
1463
|
+
{
|
|
1464
|
+
name: "externalId",
|
|
1465
|
+
type: "string",
|
|
1466
|
+
required: false,
|
|
1467
|
+
description: "This customer's id in your own system. Resolvable later via get_customer."
|
|
1468
|
+
},
|
|
1469
|
+
{
|
|
1470
|
+
name: "netN",
|
|
1471
|
+
type: "number",
|
|
1472
|
+
required: false,
|
|
1473
|
+
description: "Invoice terms in days for this customer specifically, 0-365. Omit to use the workspace default; 0 means due on issue."
|
|
1474
|
+
},
|
|
1475
|
+
{
|
|
1476
|
+
name: "acceptsMarketing",
|
|
1477
|
+
type: "boolean",
|
|
1478
|
+
required: false,
|
|
1479
|
+
description: "Whether the buyer opted into marketing email.",
|
|
1480
|
+
default: false
|
|
1481
|
+
}
|
|
1482
|
+
],
|
|
1483
|
+
requests: [{ method: "POST", path: "/api/v1/customers" }],
|
|
1484
|
+
channels: ["mcp", "zapier", "make", "n8n"],
|
|
1485
|
+
idempotent: true,
|
|
1486
|
+
guardrails: [
|
|
1487
|
+
"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."
|
|
1488
|
+
]
|
|
1489
|
+
},
|
|
1490
|
+
{
|
|
1491
|
+
id: "update_customer",
|
|
1492
|
+
title: "Update a customer",
|
|
1493
|
+
description: "Change fields on an existing customer. Only the fields supplied are touched; everything else is left alone.",
|
|
1494
|
+
kind: "write",
|
|
1495
|
+
scopes: ["customers:write"],
|
|
1496
|
+
input: [
|
|
1497
|
+
{ name: "id", type: "string", required: true, description: "Customer id." },
|
|
1498
|
+
{ name: "email", type: "string", required: false, description: "The buyer's email address." },
|
|
1499
|
+
{ name: "firstName", type: "string", required: false, description: "Given name." },
|
|
1500
|
+
{ name: "lastName", type: "string", required: false, description: "Family name." },
|
|
1501
|
+
{ name: "phone", type: "string", required: false, description: "Contact phone number." },
|
|
1502
|
+
{ name: "company", type: "string", required: false, description: "The buyer's company." },
|
|
1503
|
+
{
|
|
1504
|
+
name: "externalId",
|
|
1505
|
+
type: "string",
|
|
1506
|
+
required: false,
|
|
1507
|
+
description: "This customer's id in your own system. Pass null to clear it."
|
|
1508
|
+
},
|
|
1509
|
+
{
|
|
1510
|
+
name: "netN",
|
|
1511
|
+
type: "number",
|
|
1512
|
+
required: false,
|
|
1513
|
+
description: "Invoice terms in days for this customer specifically, 0-365."
|
|
1514
|
+
},
|
|
1515
|
+
{
|
|
1516
|
+
name: "acceptsMarketing",
|
|
1517
|
+
type: "boolean",
|
|
1518
|
+
required: false,
|
|
1519
|
+
description: "Whether the buyer opted into marketing email."
|
|
1520
|
+
}
|
|
1521
|
+
],
|
|
1522
|
+
requests: [{ method: "PATCH", path: "/api/v1/customers/:id" }],
|
|
1523
|
+
channels: ["mcp", "zapier", "make", "n8n"],
|
|
1524
|
+
idempotent: true,
|
|
1525
|
+
guardrails: [
|
|
1526
|
+
"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."
|
|
1527
|
+
]
|
|
1528
|
+
},
|
|
1529
|
+
{
|
|
1530
|
+
id: "create_checkout_link",
|
|
1531
|
+
title: "Create a checkout link",
|
|
1532
|
+
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).",
|
|
1533
|
+
kind: "write",
|
|
1534
|
+
scopes: ["carts:write", "cart_items:write", "checkout_sessions:write"],
|
|
1535
|
+
input: [
|
|
1536
|
+
{
|
|
1537
|
+
name: "lineItems",
|
|
1538
|
+
type: "array",
|
|
1539
|
+
required: true,
|
|
1540
|
+
description: "What the buyer is paying for: { name, unitPrice, quantity }. unitPrice is an integer in minor units."
|
|
1541
|
+
},
|
|
1542
|
+
{
|
|
1543
|
+
name: "returnUrl",
|
|
1544
|
+
type: "string",
|
|
1545
|
+
required: true,
|
|
1546
|
+
description: "Absolute URL the buyer lands on after paying."
|
|
1547
|
+
},
|
|
1548
|
+
{
|
|
1549
|
+
name: "cancelUrl",
|
|
1550
|
+
type: "string",
|
|
1551
|
+
required: true,
|
|
1552
|
+
description: "Absolute URL the buyer lands on if they abandon checkout."
|
|
1553
|
+
},
|
|
1554
|
+
{
|
|
1555
|
+
name: "customerId",
|
|
1556
|
+
type: "string",
|
|
1557
|
+
required: false,
|
|
1558
|
+
description: "Attach the cart to an existing customer."
|
|
1559
|
+
},
|
|
1560
|
+
{
|
|
1561
|
+
name: "customerEmail",
|
|
1562
|
+
type: "string",
|
|
1563
|
+
required: false,
|
|
1564
|
+
description: "Pre-fill the buyer email without needing a customer record."
|
|
1565
|
+
},
|
|
1566
|
+
{
|
|
1567
|
+
name: "discountCode",
|
|
1568
|
+
type: "string",
|
|
1569
|
+
required: false,
|
|
1570
|
+
description: "Discount code to apply to the session."
|
|
1571
|
+
},
|
|
1572
|
+
{
|
|
1573
|
+
name: "netN",
|
|
1574
|
+
type: "number",
|
|
1575
|
+
required: false,
|
|
1576
|
+
description: "Invoice terms in days for this cart, 0-365. Omit to pay in full at checkout."
|
|
1577
|
+
}
|
|
1578
|
+
],
|
|
1579
|
+
// Three calls: the API has no single "checkout link" endpoint, and a
|
|
1580
|
+
// checkout session requires a cart that already holds its line items.
|
|
1581
|
+
requests: [
|
|
1582
|
+
{ method: "POST", path: "/api/v1/carts" },
|
|
1583
|
+
{ method: "POST", path: "/api/v1/carts/:id/items" },
|
|
1584
|
+
{ method: "POST", path: "/api/v1/checkout/sessions" }
|
|
1585
|
+
],
|
|
1586
|
+
channels: ["mcp", "zapier", "make", "n8n"],
|
|
1587
|
+
idempotent: true,
|
|
1588
|
+
guardrails: [
|
|
1589
|
+
"The returned URL is buyer-facing and takes real payment in a production environment."
|
|
1590
|
+
]
|
|
1591
|
+
},
|
|
651
1592
|
// -------------------------------------------------------------- money/mail
|
|
652
1593
|
{
|
|
653
1594
|
id: "create_quote",
|
|
@@ -657,9 +1598,9 @@ var ACTIONS = [
|
|
|
657
1598
|
scopes: ["quotes:write"],
|
|
658
1599
|
input: [
|
|
659
1600
|
{ name: "customerId", type: "string", required: true, description: "Customer the quote is for." },
|
|
660
|
-
{ name: "
|
|
1601
|
+
{ name: "items", type: "array", required: true, description: "Quote line items: { name, unitPrice, quantity }." },
|
|
661
1602
|
{ name: "expiresAt", type: "string", required: false, description: "ISO 8601 expiry." },
|
|
662
|
-
{ name: "netN", type: "number", required: false, description: "Payment terms in days." },
|
|
1603
|
+
{ name: "netN", type: "number", required: false, description: "Payment terms in days. Omit for pay-in-full." },
|
|
663
1604
|
{ name: "notes", type: "string", required: false, description: "Buyer-visible notes." }
|
|
664
1605
|
],
|
|
665
1606
|
requests: [{ method: "POST", path: "/api/v1/quotes" }],
|
|
@@ -676,7 +1617,8 @@ var ACTIONS = [
|
|
|
676
1617
|
requests: [{ method: "POST", path: "/api/v1/quotes/:id/issue" }],
|
|
677
1618
|
channels: ["mcp", "zapier", "make", "n8n"],
|
|
678
1619
|
idempotent: true,
|
|
679
|
-
guardrails: ['Sends mail to a real buyer. Never call it speculatively or to "check" a quote.']
|
|
1620
|
+
guardrails: ['Sends mail to a real buyer. Never call it speculatively or to "check" a quote.'],
|
|
1621
|
+
destructive: true
|
|
680
1622
|
},
|
|
681
1623
|
{
|
|
682
1624
|
id: "refund_payment",
|
|
@@ -695,12 +1637,20 @@ var ACTIONS = [
|
|
|
695
1637
|
{ name: "reason", type: "string", required: false, description: "Reason recorded on the refund." }
|
|
696
1638
|
],
|
|
697
1639
|
requests: [{ method: "POST", path: "/api/v1/payments/:id/refund" }],
|
|
698
|
-
|
|
1640
|
+
// NOT zapier/make. `payment_refunds:write` is `sensitive` in the scope
|
|
1641
|
+
// registry, and `validateScopesForOAuth` rejects every sensitive scope — so
|
|
1642
|
+
// an OAuth grant, which is the only credential a public Zapier or Make app
|
|
1643
|
+
// can hold, can never carry it. Listing the action on those channels would
|
|
1644
|
+
// ship a connector action that 403s on every call. MCP and n8n keep it:
|
|
1645
|
+
// both authenticate with an `sk_` key, which CAN hold the scope.
|
|
1646
|
+
// `oauth-grantable-scopes` in the catalog tests enforces this.
|
|
1647
|
+
channels: ["mcp", "n8n"],
|
|
699
1648
|
idempotent: true,
|
|
700
1649
|
guardrails: [
|
|
701
1650
|
"Moves money and is irreversible. Confirm with the user first.",
|
|
702
1651
|
"The API treats an omitted amount as a FULL refund, so this action requires it explicitly."
|
|
703
|
-
]
|
|
1652
|
+
],
|
|
1653
|
+
destructive: true
|
|
704
1654
|
},
|
|
705
1655
|
{
|
|
706
1656
|
id: "manage_subscription",
|
|
@@ -743,19 +1693,21 @@ var ACTIONS = [
|
|
|
743
1693
|
requests: [{ method: "POST", path: "/api/v1/subscriptions/:id/cancel" }],
|
|
744
1694
|
channels: ["mcp", "zapier", "make", "n8n"],
|
|
745
1695
|
idempotent: true,
|
|
746
|
-
guardrails: ["Ends billing for a paying customer. Confirm with the user first."]
|
|
1696
|
+
guardrails: ["Ends billing for a paying customer. Confirm with the user first."],
|
|
1697
|
+
destructive: true
|
|
747
1698
|
},
|
|
748
1699
|
{
|
|
749
1700
|
id: "replay_webhook_delivery",
|
|
750
1701
|
title: "Replay a webhook delivery",
|
|
751
1702
|
description: "Re-send a past webhook delivery to its endpoint \u2014 useful after fixing a broken handler.",
|
|
752
1703
|
kind: "write",
|
|
753
|
-
scopes: ["
|
|
1704
|
+
scopes: ["webhook_deliveries:write"],
|
|
754
1705
|
input: [{ name: "id", type: "string", required: true, description: "Delivery id." }],
|
|
755
1706
|
requests: [{ method: "POST", path: "/api/v1/webhook-deliveries/:id/replay" }],
|
|
756
1707
|
channels: ["mcp", "n8n"],
|
|
757
1708
|
idempotent: true,
|
|
758
|
-
guardrails: ["The receiving system will process the event again. Confirm it is idempotent first."]
|
|
1709
|
+
guardrails: ["The receiving system will process the event again. Confirm it is idempotent first."],
|
|
1710
|
+
destructive: true
|
|
759
1711
|
},
|
|
760
1712
|
{
|
|
761
1713
|
id: "send_test_webhook",
|
|
@@ -769,7 +1721,8 @@ var ACTIONS = [
|
|
|
769
1721
|
],
|
|
770
1722
|
requests: [{ method: "POST", path: "/api/v1/webhook-endpoints/:id/test" }],
|
|
771
1723
|
channels: ["mcp", "n8n"],
|
|
772
|
-
idempotent: true
|
|
1724
|
+
idempotent: true,
|
|
1725
|
+
destructive: true
|
|
773
1726
|
}
|
|
774
1727
|
];
|
|
775
1728
|
|
|
@@ -1000,7 +1953,7 @@ function validatePayload(index, input) {
|
|
|
1000
1953
|
}
|
|
1001
1954
|
|
|
1002
1955
|
// src/docs.ts
|
|
1003
|
-
var DOCS_ORIGIN = "https://
|
|
1956
|
+
var DOCS_ORIGIN = "https://usethrottle.dev/docs";
|
|
1004
1957
|
function excerptAround(text, needle, radius = 160) {
|
|
1005
1958
|
const at = text.toLowerCase().indexOf(needle.toLowerCase());
|
|
1006
1959
|
if (at < 0) return text.slice(0, radius * 2).trim();
|
|
@@ -1084,27 +2037,35 @@ var HANDLERS = {
|
|
|
1084
2037
|
// ------------------------------------------------------------- generic read
|
|
1085
2038
|
api_get: async (ctx, args) => {
|
|
1086
2039
|
const path = String(args.path ?? "");
|
|
1087
|
-
|
|
2040
|
+
const resolved = new URL(path, "https://throttle.invalid").pathname;
|
|
2041
|
+
if (!path.startsWith("/api/v1/") || !resolved.startsWith("/api/v1/") || /(^|[\/\\])\.(\.)?([\/\\?#]|$)|%2e/i.test(path)) {
|
|
1088
2042
|
throw new Error(
|
|
1089
2043
|
`Refusing to call "${path}". api_get only reads paths beginning with /api/v1/ on the configured Throttle host.`
|
|
1090
2044
|
);
|
|
1091
2045
|
}
|
|
1092
|
-
return ctx.client.get(path);
|
|
2046
|
+
return ctx.client.get(path, void 0, { applicationId: args.applicationId });
|
|
1093
2047
|
},
|
|
1094
2048
|
// ------------------------------------------------------------------ orders
|
|
1095
|
-
search_orders: async (ctx, args) => ctx.client.list(
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
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
|
+
),
|
|
1101
2061
|
get_order: async (ctx, args) => {
|
|
1102
2062
|
const id = String(args.id);
|
|
1103
|
-
const
|
|
2063
|
+
const opts = { applicationId: args.applicationId };
|
|
2064
|
+
const order = await ctx.client.get(`/api/v1/orders/${id}`, void 0, opts);
|
|
1104
2065
|
const [payments, returns, fulfillments] = await Promise.all([
|
|
1105
|
-
optional(() => ctx.client.list(`/api/v1/orders/${id}/payments
|
|
1106
|
-
optional(() => ctx.client.list(`/api/v1/orders/${id}/returns
|
|
1107
|
-
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))
|
|
1108
2069
|
]);
|
|
1109
2070
|
return {
|
|
1110
2071
|
order,
|
|
@@ -1114,13 +2075,23 @@ var HANDLERS = {
|
|
|
1114
2075
|
};
|
|
1115
2076
|
},
|
|
1116
2077
|
// --------------------------------------------------------------- customers
|
|
1117
|
-
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
|
+
),
|
|
1118
2088
|
get_customer: async (ctx, args) => {
|
|
1119
2089
|
const id = String(args.id);
|
|
1120
|
-
const
|
|
2090
|
+
const opts = { applicationId: args.applicationId };
|
|
2091
|
+
const customer = await ctx.client.get(`/api/v1/customers/${id}`, void 0, opts);
|
|
1121
2092
|
const [addresses, subscriptions] = await Promise.all([
|
|
1122
|
-
optional(() => ctx.client.list(`/api/v1/customers/${id}/addresses
|
|
1123
|
-
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))
|
|
1124
2095
|
]);
|
|
1125
2096
|
return {
|
|
1126
2097
|
customer,
|
|
@@ -1129,45 +2100,155 @@ var HANDLERS = {
|
|
|
1129
2100
|
};
|
|
1130
2101
|
},
|
|
1131
2102
|
// ----------------------------------------------------------- subscriptions
|
|
1132
|
-
list_subscriptions: async (ctx, args) => ctx.client.list(
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
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
|
|
1136
2116
|
}),
|
|
1137
|
-
get_subscription: async (ctx, args) => ctx.client.get(`/api/v1/subscriptions/${String(args.id)}`),
|
|
1138
2117
|
// ------------------------------------------------------------------ quotes
|
|
1139
|
-
search_quotes: async (ctx, args) => ctx.client.list(
|
|
1140
|
-
|
|
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
|
+
}),
|
|
2131
|
+
// ---------------------------------------------------------------- invoices
|
|
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 }),
|
|
2138
|
+
// --------------------------------------------------------------- discounts
|
|
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
|
|
2154
|
+
}),
|
|
2155
|
+
// ---------------------------------------------------------------- payments
|
|
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
|
+
),
|
|
2174
|
+
get_payment: async (ctx, args) => {
|
|
2175
|
+
const id = String(args.id);
|
|
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
|
+
);
|
|
2181
|
+
return {
|
|
2182
|
+
payment,
|
|
2183
|
+
transactions: transactions.ok ? transactions.value.data : transactions.unavailable
|
|
2184
|
+
};
|
|
2185
|
+
},
|
|
1141
2186
|
// ---------------------------------------------------------------- invoices
|
|
1142
|
-
|
|
1143
|
-
|
|
2187
|
+
get_invoice: async (ctx, args) => ctx.client.get(`/api/v1/invoices/${String(args.id)}`, void 0, {
|
|
2188
|
+
applicationId: args.applicationId
|
|
2189
|
+
}),
|
|
2190
|
+
// ------------------------------------------------------------------ carts
|
|
2191
|
+
list_abandoned_carts: async (ctx, args) => {
|
|
2192
|
+
const opts = { applicationId: args.applicationId };
|
|
2193
|
+
const [carts, summary] = await Promise.all([
|
|
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
|
+
)
|
|
2198
|
+
]);
|
|
2199
|
+
return {
|
|
2200
|
+
carts: carts.ok ? carts.value.data : carts.unavailable,
|
|
2201
|
+
summary: summary.ok ? summary.value : summary.unavailable
|
|
2202
|
+
};
|
|
2203
|
+
},
|
|
1144
2204
|
// --------------------------------------------------------------- debugging
|
|
1145
|
-
search_events: async (ctx, args) => ctx.client.list(
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
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
|
|
2227
|
+
}),
|
|
2228
|
+
send_test_webhook: async (ctx, args) => ctx.client.post(`/api/v1/webhook-endpoints/${String(args.id)}/test`, {
|
|
2229
|
+
body: { eventType: args.eventType },
|
|
2230
|
+
idempotencyKey: `mcp-send-test-webhook-${String(args.id)}-${Date.now()}`
|
|
1150
2231
|
}),
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
status: args.status,
|
|
1154
|
-
limit: args.limit
|
|
2232
|
+
replay_webhook_delivery: async (ctx, args) => ctx.client.post(`/api/v1/webhook-deliveries/${String(args.id)}/replay`, {
|
|
2233
|
+
idempotencyKey: `mcp-replay-webhook-delivery-${String(args.id)}-${Date.now()}`
|
|
1155
2234
|
}),
|
|
1156
|
-
get_webhook_delivery: async (ctx, args) => ctx.client.get(`/api/v1/webhook-deliveries/${String(args.id)}`),
|
|
1157
2235
|
explain_checkout_failure: async (ctx, args) => {
|
|
1158
2236
|
const id = String(args.id);
|
|
1159
|
-
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
|
+
);
|
|
1160
2241
|
if (!session.ok) {
|
|
1161
2242
|
return {
|
|
1162
|
-
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).`
|
|
1163
2244
|
};
|
|
1164
2245
|
}
|
|
1165
2246
|
const cartId = session.value?.cartId ?? void 0;
|
|
1166
2247
|
const [cart, sessionEvents, cartEvents, deliveries] = await Promise.all([
|
|
1167
|
-
optional(() => ctx.client.get(`/api/v1/checkout-sessions/${id}/cart
|
|
1168
|
-
optional(() => ctx.client.list("/api/v1/events", { entityId: id, limit: 25 })),
|
|
1169
|
-
cartId ? optional(() => ctx.client.list("/api/v1/events", { entityId: cartId, limit: 25 })) : Promise.resolve({ ok: true, value: { data: [], pagination: null } }),
|
|
1170
|
-
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))
|
|
1171
2252
|
]);
|
|
1172
2253
|
return {
|
|
1173
2254
|
session: session.value,
|
|
@@ -1198,7 +2279,11 @@ var HANDLERS = {
|
|
|
1198
2279
|
idempotencyKey: `mcp-seed-customer-${stamp}`
|
|
1199
2280
|
});
|
|
1200
2281
|
const cart = await ctx.client.post("/api/v1/carts", {
|
|
1201
|
-
|
|
2282
|
+
// Unlike /api/v1/customers, cart creation requires applicationId
|
|
2283
|
+
// explicitly in the body (it's cross-checked against the caller's own
|
|
2284
|
+
// authenticated application, not inferred) -- omitting it is a real
|
|
2285
|
+
// 400 missing_application_id from the live API, not a hypothetical.
|
|
2286
|
+
body: { applicationId: ctx.identity.application?.id, customerId: customer?.id },
|
|
1202
2287
|
idempotencyKey: `mcp-seed-cart-${stamp}`
|
|
1203
2288
|
});
|
|
1204
2289
|
const requested = Array.isArray(args.lineItems) && args.lineItems.length > 0 ? args.lineItems : [{ name: "Seed widget", unitPrice: 2500, quantity: 2 }];
|
|
@@ -1223,23 +2308,253 @@ var HANDLERS = {
|
|
|
1223
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."
|
|
1224
2309
|
};
|
|
1225
2310
|
},
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
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
|
+
},
|
|
2378
|
+
// -------------------------------------------------------------- money/mail
|
|
2379
|
+
manage_subscription: async (ctx, args) => {
|
|
2380
|
+
const id = String(args.id);
|
|
2381
|
+
const action = String(args.action);
|
|
2382
|
+
if (action !== "pause" && action !== "resume") {
|
|
2383
|
+
throw new Error(`manage_subscription: action must be "pause" or "resume", got "${action}".`);
|
|
2384
|
+
}
|
|
2385
|
+
return ctx.client.post(`/api/v1/subscriptions/${id}/${action}`, {
|
|
2386
|
+
idempotencyKey: `mcp-manage-subscription-${id}-${action}-${Date.now()}`
|
|
2387
|
+
});
|
|
2388
|
+
},
|
|
2389
|
+
create_quote: async (ctx, args) => (
|
|
2390
|
+
// The real route's body shape differs from this tool's flatter,
|
|
2391
|
+
// model-facing input: line items are "items" (not "lineItems"), Net-N
|
|
2392
|
+
// terms are nested under paymentTerms.netN with an explicit mode (not a
|
|
2393
|
+
// flat "netN"), and the buyer-visible note field is "buyerNote" (not
|
|
2394
|
+
// "notes"). Confirmed against packages/platform-api/src/validation/quotes.ts's
|
|
2395
|
+
// actual zod schema after a live call 400'd with unrecognized_keys.
|
|
2396
|
+
ctx.client.post("/api/v1/quotes", {
|
|
2397
|
+
body: {
|
|
2398
|
+
customerId: args.customerId,
|
|
2399
|
+
items: args.items,
|
|
2400
|
+
expiresAt: args.expiresAt,
|
|
2401
|
+
paymentTerms: args.netN !== void 0 ? { mode: "net_terms", netN: args.netN } : void 0,
|
|
2402
|
+
buyerNote: args.notes
|
|
2403
|
+
},
|
|
2404
|
+
idempotencyKey: `mcp-create-quote-${String(args.customerId)}-${Date.now()}`
|
|
2405
|
+
})
|
|
2406
|
+
),
|
|
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
|
+
),
|
|
2505
|
+
// ------------------------------------------------------------- extensions
|
|
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
|
+
),
|
|
2511
|
+
// ------------------------------------------------------------- connectors
|
|
2512
|
+
get_connector_routing: async (ctx, args) => {
|
|
2513
|
+
const opts = { applicationId: args.applicationId };
|
|
2514
|
+
const [paymentRouting, routingConnectors, cardDecline, otherDecline] = await Promise.all([
|
|
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))
|
|
2519
|
+
]);
|
|
2520
|
+
return {
|
|
2521
|
+
paymentRouting: paymentRouting.ok ? paymentRouting.value : paymentRouting.unavailable,
|
|
2522
|
+
routingConnectors: routingConnectors.ok ? routingConnectors.value : routingConnectors.unavailable,
|
|
2523
|
+
cardDecline: cardDecline.ok ? cardDecline.value : cardDecline.unavailable,
|
|
2524
|
+
otherDecline: otherDecline.ok ? otherDecline.value : otherDecline.unavailable
|
|
2525
|
+
};
|
|
2526
|
+
},
|
|
2527
|
+
get_integration_status: async (ctx, args) => {
|
|
2528
|
+
const opts = { applicationId: args.applicationId };
|
|
1229
2529
|
const [connectors, endpoints, coverage] = await Promise.all([
|
|
1230
|
-
optional(() => ctx.client.list("/api/v1/connectors")),
|
|
1231
|
-
optional(() => ctx.client.list("/api/v1/webhook-endpoints")),
|
|
1232
|
-
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))
|
|
1233
2533
|
]);
|
|
2534
|
+
const overridden = Boolean(args.applicationId) && args.applicationId !== ctx.identity.application?.id;
|
|
1234
2535
|
return {
|
|
1235
2536
|
environment: ctx.identity.environment,
|
|
1236
2537
|
workspace: ctx.identity.workspace,
|
|
1237
|
-
|
|
2538
|
+
applicationId: args.applicationId ?? ctx.identity.application?.id ?? null,
|
|
2539
|
+
application: overridden ? void 0 : ctx.identity.application,
|
|
1238
2540
|
connectors: connectors.ok ? connectors.value.data : connectors.unavailable,
|
|
1239
2541
|
webhookEndpoints: endpoints.ok ? endpoints.value.data : endpoints.unavailable,
|
|
1240
2542
|
unsubscribedEvents: coverage.ok ? coverage.value : coverage.unavailable
|
|
1241
2543
|
};
|
|
1242
|
-
}
|
|
2544
|
+
},
|
|
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.
|
|
2549
|
+
get_workspace_settings: async (ctx) => ctx.client.get("/api/v1/merchants/me"),
|
|
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")
|
|
1243
2558
|
};
|
|
1244
2559
|
|
|
1245
2560
|
// src/input.ts
|
|
@@ -1274,15 +2589,548 @@ function applyModifiers(field, type) {
|
|
|
1274
2589
|
return field.default === void 0 ? described.optional() : described.default(field.default);
|
|
1275
2590
|
}
|
|
1276
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
|
+
|
|
1277
3124
|
// src/server.ts
|
|
1278
|
-
var VERSION = true ? "0.
|
|
3125
|
+
var VERSION = true ? "0.2.1" : "0.0.0-dev";
|
|
1279
3126
|
function registerableActions(policy) {
|
|
1280
3127
|
const isProduction = policy.identity.environment.providerEnvironment === "production";
|
|
1281
3128
|
return actionsForChannel("mcp").filter((action) => {
|
|
1282
3129
|
if (!HANDLERS[action.id]) return false;
|
|
3130
|
+
if (action.credentialScoped && policy.allowCredentialScoped === false) return false;
|
|
1283
3131
|
if (!canRunAction(policy.identity.scopes, action)) return false;
|
|
1284
3132
|
if (action.kind === "read") return true;
|
|
1285
|
-
if (action.sandboxOnly) return !isProduction;
|
|
3133
|
+
if (action.sandboxOnly) return !isProduction && (policy.allowSandboxWrites ?? true);
|
|
1286
3134
|
if (!policy.allowWrites) return false;
|
|
1287
3135
|
return !isProduction || policy.allowLiveWrites;
|
|
1288
3136
|
});
|
|
@@ -1299,11 +3147,14 @@ function buildInstructions(identity, actions, policy) {
|
|
|
1299
3147
|
`Throttle MCP ${VERSION}. Connected to ${where}, environment "${identity.environment.slug}" (${isProduction ? "PRODUCTION \u2014 live data" : "non-production \u2014 not live data"}).`,
|
|
1300
3148
|
"",
|
|
1301
3149
|
"Conventions:",
|
|
1302
|
-
"-
|
|
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.",
|
|
1303
3153
|
'- List tools return { data, pagination }. When pagination.hasMore is true, pass pagination.cursor back as "cursor" rather than raising "limit".',
|
|
1304
3154
|
"- Amounts are integer minor units (2500 = $25.00).",
|
|
1305
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.",
|
|
1306
|
-
"- 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."
|
|
1307
3158
|
];
|
|
1308
3159
|
if (liveWrites.length === 0) {
|
|
1309
3160
|
lines.push(
|
|
@@ -1321,8 +3172,31 @@ function buildInstructions(identity, actions, policy) {
|
|
|
1321
3172
|
}
|
|
1322
3173
|
return lines.join("\n");
|
|
1323
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
|
+
}
|
|
1324
3197
|
function createServer(options) {
|
|
1325
|
-
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);
|
|
1326
3200
|
const server = new McpServer(
|
|
1327
3201
|
{ name: "throttle", version: VERSION },
|
|
1328
3202
|
{ instructions: buildInstructions(options.identity, actions, options) }
|
|
@@ -1335,25 +3209,57 @@ function createServer(options) {
|
|
|
1335
3209
|
};
|
|
1336
3210
|
for (const action of actions) {
|
|
1337
3211
|
const handler = HANDLERS[action.id];
|
|
1338
|
-
server.
|
|
3212
|
+
server.registerTool(
|
|
1339
3213
|
action.id,
|
|
1340
|
-
|
|
1341
|
-
|
|
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
|
+
},
|
|
1342
3230
|
async (args) => {
|
|
1343
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
|
+
}
|
|
1344
3238
|
const result = await handler(ctx, args ?? {});
|
|
1345
|
-
|
|
3239
|
+
const shown = redactPii ? redactShopperPii(project(result, OAUTH_PROJECTIONS[action.id])) : result;
|
|
3240
|
+
return { content: [{ type: "text", text: formatResult(shown) }] };
|
|
1346
3241
|
} catch (error) {
|
|
1347
3242
|
return {
|
|
1348
3243
|
content: [{ type: "text", text: formatError(error) }],
|
|
1349
|
-
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
|
+
} : {}
|
|
1350
3252
|
};
|
|
1351
3253
|
}
|
|
1352
3254
|
}
|
|
1353
3255
|
);
|
|
1354
3256
|
}
|
|
3257
|
+
if (options.authChallenge) declareSecuritySchemes(server, actions);
|
|
1355
3258
|
return server;
|
|
1356
3259
|
}
|
|
3260
|
+
function isCredentialRejection(error) {
|
|
3261
|
+
return error?.statusCode === 401;
|
|
3262
|
+
}
|
|
1357
3263
|
function describeForModel(action) {
|
|
1358
3264
|
const parts = [action.description];
|
|
1359
3265
|
if (action.guardrails && action.guardrails.length > 0) {
|
|
@@ -1362,6 +3268,26 @@ function describeForModel(action) {
|
|
|
1362
3268
|
return parts.join(" ");
|
|
1363
3269
|
}
|
|
1364
3270
|
|
|
3271
|
+
// src/indexes.ts
|
|
3272
|
+
import { readFileSync } from "fs";
|
|
3273
|
+
function loadIndex(file, empty, label) {
|
|
3274
|
+
try {
|
|
3275
|
+
return JSON.parse(readFileSync(new URL(`./${file}`, import.meta.url), "utf8"));
|
|
3276
|
+
} catch {
|
|
3277
|
+
process.stderr.write(
|
|
3278
|
+
`throttle-mcp: ${label} index missing (${file}); that tool will return no results. Run "pnpm --filter @usethrottle/mcp build:indexes".
|
|
3279
|
+
`
|
|
3280
|
+
);
|
|
3281
|
+
return empty;
|
|
3282
|
+
}
|
|
3283
|
+
}
|
|
3284
|
+
function loadDocsIndex() {
|
|
3285
|
+
return loadIndex("docs-index.json", { generatedAt: "", entries: [] }, "docs");
|
|
3286
|
+
}
|
|
3287
|
+
function loadContractIndex() {
|
|
3288
|
+
return loadIndex("contract-index.json", { generatedAt: "", operations: [] }, "contract");
|
|
3289
|
+
}
|
|
3290
|
+
|
|
1365
3291
|
// src/index.ts
|
|
1366
3292
|
var DEFAULT_API = "https://api.usethrottle.dev";
|
|
1367
3293
|
function fail(message) {
|
|
@@ -1413,17 +3339,6 @@ function parseArgs(argv) {
|
|
|
1413
3339
|
allowLiveWrites: args.includes("--allow-live-writes")
|
|
1414
3340
|
};
|
|
1415
3341
|
}
|
|
1416
|
-
function loadIndex(file, empty, label) {
|
|
1417
|
-
try {
|
|
1418
|
-
return JSON.parse(readFileSync(new URL(`./${file}`, import.meta.url), "utf8"));
|
|
1419
|
-
} catch {
|
|
1420
|
-
process.stderr.write(
|
|
1421
|
-
`throttle-mcp: ${label} index missing (${file}); that tool will return no results. Run "pnpm --filter @usethrottle/mcp build:indexes".
|
|
1422
|
-
`
|
|
1423
|
-
);
|
|
1424
|
-
return empty;
|
|
1425
|
-
}
|
|
1426
|
-
}
|
|
1427
3342
|
async function main() {
|
|
1428
3343
|
const options = parseArgs(process.argv);
|
|
1429
3344
|
const client = new ThrottleClient({ apiKey: options.apiKey, apiBaseUrl: options.apiBaseUrl });
|
|
@@ -1436,12 +3351,8 @@ async function main() {
|
|
|
1436
3351
|
`could not identify the credential via GET /api/v1/whoami (${e?.code ?? "error"}: ${e?.message ?? String(error)}). Check the key and --api URL.`
|
|
1437
3352
|
);
|
|
1438
3353
|
}
|
|
1439
|
-
const docs =
|
|
1440
|
-
const contract =
|
|
1441
|
-
"contract-index.json",
|
|
1442
|
-
{ generatedAt: "", operations: [] },
|
|
1443
|
-
"contract"
|
|
1444
|
-
);
|
|
3354
|
+
const docs = loadDocsIndex();
|
|
3355
|
+
const contract = loadContractIndex();
|
|
1445
3356
|
const server = createServer({
|
|
1446
3357
|
identity,
|
|
1447
3358
|
client,
|