@yawlabs/lemonsqueezy-mcp 0.8.1 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +28 -3
  2. package/dist/index.js +149 -2
  3. package/package.json +2 -1
package/README.md CHANGED
@@ -2,6 +2,10 @@
2
2
 
3
3
  MCP server for the [LemonSqueezy](https://lemonsqueezy.com) API. Manage your store, products, customers, subscriptions, discounts, license keys, and more from any MCP-compatible AI assistant.
4
4
 
5
+ [![Add to mcp.hosting](https://mcp.hosting/install-button.svg)](https://mcp.hosting/install?name=LemonSqueezy&command=npx&args=-y%2C%40yawlabs%2Flemonsqueezy-mcp&description=LemonSqueezy%20store%20management%20-%20products%2C%20orders%2C%20subscriptions%2C%20license%20keys&source=https%3A%2F%2Fgithub.com%2FYawLabs%2Flemonsqueezy-mcp)
6
+
7
+ One click adds this to your [mcp.hosting](https://mcp.hosting) account so it syncs to every MCP client you use. Or install manually below.
8
+
5
9
  ## Quick start
6
10
 
7
11
  ```bash
@@ -220,6 +224,8 @@ All configuration is via environment variables. Only `LEMONSQUEEZY_API_KEY` (or
220
224
  | `LEMONSQUEEZY_ALLOWED_STORE_IDS` | Comma-separated allowlist of store IDs. When set: (1) any tool whose input includes a `storeId` rejects calls to a non-allowed store; (2) tools that *accept* a `storeId` filter (e.g. `ls_list_orders`, `ls_list_subscriptions`) require it — calls without one are blocked so a missing filter cannot return data from every store the API key can see. Tools with no `storeId` field at all (e.g. `ls_refund_order`, `ls_cancel_subscription`, `ls_archive_customer`, `ls_delete_webhook`, `ls_delete_discount`, `ls_update_license_key`, `ls_list_stores`) route by their own resource ID and are **not** gated by this allowlist. For those, the only authoritative store boundary is the API key itself — pair this setting with a LemonSqueezy API key scoped to the same store(s), and pair with `LEMONSQUEEZY_MAX_REFUND_AMOUNT_CENTS` / `LEMONSQUEEZY_DESTRUCTIVE_RATE_LIMIT` for additional defense in depth. |
221
225
  | `LEMONSQUEEZY_MAX_REFUND_AMOUNT_CENTS` | Rejects `ls_refund_order` and `ls_refund_subscription_invoice` calls above this amount. |
222
226
  | `LEMONSQUEEZY_DESTRUCTIVE_RATE_LIMIT` | Max destructive tool calls per 60-second rolling window. In-process limit — per MCP server instance, not global; each `npx` cold start resets the window. Counts include `ls_update_license_key` calls that set `disabled: true`, and `ls_update_subscription` calls that pause or switch plan. |
227
+ | `LEMONSQUEEZY_DISABLE_CLASSES` | Comma-separated list of [authority classes](#authority-classes) to refuse outright. Any tool whose class is listed returns a `guardrail_block` before the API call is attempted. Example: `LEMONSQUEEZY_DISABLE_CLASSES=money,recurring,pii` lets an agent run reads but blocks refunds, subscription changes, and customer-record access. Unknown class names throw at server startup. |
228
+ | `LEMONSQUEEZY_RATE_LIMIT_PER_CLASS` | Per-class rolling rate limits, comma-separated. Each entry is `class:N`, `class:N/m`, or `class:N/h` (bare numbers default to per-minute). Example: `money:2/h,recurring:5/h,key:10/m`. Composes with `LEMONSQUEEZY_DESTRUCTIVE_RATE_LIMIT` — both must pass. In-process per server instance. |
223
229
  | `LEMONSQUEEZY_LOG` | Structured-log verbosity to stderr. Set to `all` (or legacy `json`) to log every tool and HTTP call, `audit` to log only destructive-call audit entries plus errors (recommended for production), `error` to log only failures. Unset: no logs. Destructive calls are tagged `audit: true` and include their inputs. |
224
230
 
225
231
  ### Logging format
@@ -230,6 +236,24 @@ Each line: `{ts, event, tool?, method?, path?, status, latency_ms, request_id?,
230
236
 
231
237
  HTTP errors include the upstream `X-Request-Id` when present, so support tickets to LemonSqueezy can reference the exact call.
232
238
 
239
+ ## Authority classes
240
+
241
+ Every tool is tagged with an **authority class** — a label for the kind of business authority a caller needs to invoke it. The class is separate from the binary destructive/read-only annotation: a customer-record read and a product list are both reads, but only one returns PII; a checkout creation and a refund are both writes, but only one moves money. Granular classes let an operator gate by the dimension that matters, instead of being stuck with a single "destructive" toggle.
242
+
243
+ | Class | What it covers | Example tools |
244
+ | --- | --- | --- |
245
+ | `read` | Safe reads (list/get) that don't return customer PII as the primary payload. | `ls_list_orders`, `ls_get_product`, `ls_validate_license` |
246
+ | `pii` | Reads or writes whose primary payload is a customer record. | `ls_list_customers`, `ls_create_customer`, `ls_archive_customer` |
247
+ | `mutate` | Safe mutations: checkouts, discounts, invoice generation, usage records. | `ls_create_checkout`, `ls_create_discount`, `ls_generate_order_invoice` |
248
+ | `money` | Money movement. Irreversible at the payment layer. | `ls_refund_order`, `ls_refund_subscription_invoice` |
249
+ | `recurring` | Subscription state changes that affect recurring revenue. | `ls_update_subscription`, `ls_cancel_subscription`, `ls_update_subscription_item` |
250
+ | `key` | License-key admin (activate, deactivate, disable, change activation limit). | `ls_update_license_key`, `ls_activate_license`, `ls_deactivate_license` |
251
+ | `webhook` | Webhook configuration — affects the trust surface other systems rely on. | `ls_create_webhook`, `ls_update_webhook`, `ls_delete_webhook` |
252
+
253
+ Note: `ls_get_order` returns customer fields incidentally, but its primary payload is the order — it stays in `read`, not `pii`. The class is reserved for tools whose *primary purpose* is the customer record. If you need to deny all access to customer-shaped data, prefer a scoped API key over class-disable alone.
254
+
255
+ `LEMONSQUEEZY_DISABLE_CLASSES` blocks a class outright. `LEMONSQUEEZY_RATE_LIMIT_PER_CLASS` caps the call rate per class. Both are opt-in; with neither set, behavior is unchanged from prior versions.
256
+
233
257
  ## Resources
234
258
 
235
259
  The server exposes one MCP Resource for clients that prefer structural retrieval over parsing stderr:
@@ -244,9 +268,10 @@ For unattended/agentic use against a live store, we recommend:
244
268
 
245
269
  1. Use a LemonSqueezy API key scoped to the specific store(s) the agent may touch — this is the only authoritative store boundary for tools that route by their own resource ID (refunds, cancels, archive, delete-webhook, etc.). Set `LEMONSQUEEZY_ALLOWED_STORE_IDS` to the same set as a defense-in-depth gate on the tools that *do* take a `storeId`.
246
270
  2. Set `LEMONSQUEEZY_MAX_REFUND_AMOUNT_CENTS` to a per-call cap well below any single-refund expectation.
247
- 3. Set `LEMONSQUEEZY_DESTRUCTIVE_RATE_LIMIT` to a small number (e.g. 5/min) as a runaway-agent circuit breaker.
248
- 4. Set `LEMONSQUEEZY_LOG=audit` and ship stderr to your log aggregator. The `audit` level keeps every destructive-call entry plus errors but drops successful reads so log volume stays bounded over weeks of operation. Alert on `status: "guardrail_block"` or elevated error rates per tool. Use `LEMONSQUEEZY_LOG=all` while debugging.
249
- 5. Run `LEMONSQUEEZY_API_KEY_COMMAND` against a vault-backed secret so credentials can rotate without restarting the server process. The API client invalidates its in-process key cache automatically on a 401/403, so a rotated upstream key picks up on the next request rather than waiting on the 1h TTL.
271
+ 3. Set `LEMONSQUEEZY_DESTRUCTIVE_RATE_LIMIT` to a small number (e.g. 5/min) as a runaway-agent circuit breaker. For finer control, add `LEMONSQUEEZY_RATE_LIMIT_PER_CLASS=money:2/h,recurring:5/h,key:10/m` so each [authority class](#authority-classes) has its own ceiling.
272
+ 4. If a class shouldn't be reachable at all (e.g. an analytics agent that needs only `read`), set `LEMONSQUEEZY_DISABLE_CLASSES` to refuse the rest outright calls return a `guardrail_block` before the API is touched.
273
+ 5. Set `LEMONSQUEEZY_LOG=audit` and ship stderr to your log aggregator. The `audit` level keeps every destructive-call entry plus errors but drops successful reads so log volume stays bounded over weeks of operation. Alert on `status: "guardrail_block"` or elevated error rates per tool. Use `LEMONSQUEEZY_LOG=all` while debugging.
274
+ 6. Run `LEMONSQUEEZY_API_KEY_COMMAND` against a vault-backed secret so credentials can rotate without restarting the server process. The API client invalidates its in-process key cache automatically on a 401/403, so a rotated upstream key picks up on the next request rather than waiting on the 1h TTL.
250
275
 
251
276
  What the server does **not** do and you must own at the caller level:
252
277
 
package/dist/index.js CHANGED
@@ -30224,8 +30224,13 @@ var GuardrailError = class extends Error {
30224
30224
  this.name = "GuardrailError";
30225
30225
  }
30226
30226
  };
30227
+ var AUTHORITY_CLASSES = ["read", "pii", "mutate", "money", "recurring", "key", "webhook"];
30228
+ function isAuthorityClass(s) {
30229
+ return AUTHORITY_CLASSES.includes(s);
30230
+ }
30227
30231
  var cachedOptions = null;
30228
30232
  var destructiveTimestamps = [];
30233
+ var classTimestamps = /* @__PURE__ */ new Map();
30229
30234
  function readNumber(name, raw) {
30230
30235
  if (raw === void 0 || raw.trim() === "") return null;
30231
30236
  const n = Number(raw);
@@ -30234,6 +30239,59 @@ function readNumber(name, raw) {
30234
30239
  }
30235
30240
  return n;
30236
30241
  }
30242
+ function parseDisabledClasses(raw) {
30243
+ if (!raw || raw.trim() === "") return null;
30244
+ const out = /* @__PURE__ */ new Set();
30245
+ for (const part of raw.split(",")) {
30246
+ const cls = part.trim();
30247
+ if (!cls) continue;
30248
+ if (!isAuthorityClass(cls)) {
30249
+ throw new Error(
30250
+ `LEMONSQUEEZY_DISABLE_CLASSES contains unknown class ${JSON.stringify(cls)} (expected one of: ${AUTHORITY_CLASSES.join(", ")})`
30251
+ );
30252
+ }
30253
+ out.add(cls);
30254
+ }
30255
+ return out.size > 0 ? out : null;
30256
+ }
30257
+ function parseClassRateLimits(raw) {
30258
+ if (!raw || raw.trim() === "") return null;
30259
+ const out = /* @__PURE__ */ new Map();
30260
+ for (const part of raw.split(",")) {
30261
+ const segment = part.trim();
30262
+ if (!segment) continue;
30263
+ const colon = segment.indexOf(":");
30264
+ if (colon < 0) {
30265
+ throw new Error(
30266
+ `LEMONSQUEEZY_RATE_LIMIT_PER_CLASS entry missing colon: ${JSON.stringify(segment)} (expected class:N or class:N/h)`
30267
+ );
30268
+ }
30269
+ const cls = segment.slice(0, colon).trim();
30270
+ if (!isAuthorityClass(cls)) {
30271
+ throw new Error(
30272
+ `LEMONSQUEEZY_RATE_LIMIT_PER_CLASS contains unknown class ${JSON.stringify(cls)} (expected one of: ${AUTHORITY_CLASSES.join(", ")})`
30273
+ );
30274
+ }
30275
+ const specRaw = segment.slice(colon + 1).trim();
30276
+ const slash = specRaw.indexOf("/");
30277
+ const numPart = slash >= 0 ? specRaw.slice(0, slash).trim() : specRaw;
30278
+ const unitPart = slash >= 0 ? specRaw.slice(slash + 1).trim().toLowerCase() : "m";
30279
+ const n = Number(numPart);
30280
+ if (!Number.isFinite(n) || n < 0 || numPart === "") {
30281
+ throw new Error(`LEMONSQUEEZY_RATE_LIMIT_PER_CLASS entry has invalid number: ${JSON.stringify(segment)}`);
30282
+ }
30283
+ let windowMs;
30284
+ if (unitPart === "m") windowMs = 6e4;
30285
+ else if (unitPart === "h") windowMs = 36e5;
30286
+ else {
30287
+ throw new Error(
30288
+ `LEMONSQUEEZY_RATE_LIMIT_PER_CLASS entry has invalid unit (expected m or h): ${JSON.stringify(segment)}`
30289
+ );
30290
+ }
30291
+ out.set(cls, { limit: n, windowMs });
30292
+ }
30293
+ return out.size > 0 ? out : null;
30294
+ }
30237
30295
  function loadOptions() {
30238
30296
  if (cachedOptions) return cachedOptions;
30239
30297
  const allowed = process.env.LEMONSQUEEZY_ALLOWED_STORE_IDS;
@@ -30249,7 +30307,9 @@ function loadOptions() {
30249
30307
  rateLimitPerMinute: readNumber(
30250
30308
  "LEMONSQUEEZY_DESTRUCTIVE_RATE_LIMIT",
30251
30309
  process.env.LEMONSQUEEZY_DESTRUCTIVE_RATE_LIMIT
30252
- )
30310
+ ),
30311
+ disabledClasses: parseDisabledClasses(process.env.LEMONSQUEEZY_DISABLE_CLASSES),
30312
+ classRateLimits: parseClassRateLimits(process.env.LEMONSQUEEZY_RATE_LIMIT_PER_CLASS)
30253
30313
  };
30254
30314
  return cachedOptions;
30255
30315
  }
@@ -30312,6 +30372,30 @@ function isDestructiveCall(tool, input) {
30312
30372
  if (typeof tool.isDestructive === "function") return tool.isDestructive(input);
30313
30373
  return tool.annotations?.destructiveHint === true;
30314
30374
  }
30375
+ function checkClassAllowed(cls) {
30376
+ const o = loadOptions();
30377
+ if (!o.disabledClasses) return;
30378
+ if (o.disabledClasses.has(cls)) {
30379
+ throw new GuardrailError(`Tool authority class ${JSON.stringify(cls)} is disabled by LEMONSQUEEZY_DISABLE_CLASSES`);
30380
+ }
30381
+ }
30382
+ function checkClassRateLimit(cls, now = Date.now()) {
30383
+ const o = loadOptions();
30384
+ if (!o.classRateLimits) return;
30385
+ const spec = o.classRateLimits.get(cls);
30386
+ if (!spec) return;
30387
+ const cutoff = now - spec.windowMs;
30388
+ const list = (classTimestamps.get(cls) ?? []).filter((t) => t > cutoff);
30389
+ if (list.length >= spec.limit) {
30390
+ const unit = spec.windowMs === 6e4 ? "min" : spec.windowMs === 36e5 ? "hour" : `${spec.windowMs}ms`;
30391
+ classTimestamps.set(cls, list);
30392
+ throw new GuardrailError(
30393
+ `Class ${JSON.stringify(cls)} rate limit exceeded (${spec.limit}/${unit}). Wait and retry.`
30394
+ );
30395
+ }
30396
+ list.push(now);
30397
+ classTimestamps.set(cls, list);
30398
+ }
30315
30399
 
30316
30400
  // src/logger.ts
30317
30401
  function getLogLevel() {
@@ -30869,6 +30953,7 @@ async function apiDelete(path) {
30869
30953
  var affiliateTools = [
30870
30954
  {
30871
30955
  name: "ls_get_affiliate",
30956
+ authorityClass: "read",
30872
30957
  description: "Get a specific affiliate by ID, including commission rate, status, and earnings.",
30873
30958
  annotations: {
30874
30959
  title: "Get affiliate",
@@ -30885,6 +30970,7 @@ var affiliateTools = [
30885
30970
  },
30886
30971
  {
30887
30972
  name: "ls_list_affiliates",
30973
+ authorityClass: "read",
30888
30974
  description: "List all affiliates for the authenticated user's stores, optionally filtered by user email. Results are paginated \u2014 check meta.page in the response for currentPage, lastPage, and total. Cross-store note: when LEMONSQUEEZY_ALLOWED_STORE_IDS is set, this tool can still return affiliates tied to non-allowed stores -- the endpoint has no parent ID filter to scope by. Pair with a scoped LemonSqueezy API key for true cross-store enforcement -- the API key's visibility is the true boundary.",
30889
30975
  annotations: {
30890
30976
  title: "List affiliates",
@@ -30907,6 +30993,7 @@ var affiliateTools = [
30907
30993
  var checkoutTools = [
30908
30994
  {
30909
30995
  name: "ls_get_checkout",
30996
+ authorityClass: "read",
30910
30997
  description: "Get a specific checkout by ID, including URL, expiry, and custom data.",
30911
30998
  annotations: {
30912
30999
  title: "Get checkout",
@@ -30923,6 +31010,7 @@ var checkoutTools = [
30923
31010
  },
30924
31011
  {
30925
31012
  name: "ls_list_checkouts",
31013
+ authorityClass: "read",
30926
31014
  description: "List all checkouts, optionally filtered by store or variant. Results are paginated \u2014 check meta.page in the response for currentPage, lastPage, and total.",
30927
31015
  annotations: {
30928
31016
  title: "List checkouts",
@@ -30942,6 +31030,7 @@ var checkoutTools = [
30942
31030
  },
30943
31031
  {
30944
31032
  name: "ls_create_checkout",
31033
+ authorityClass: "mutate",
30945
31034
  description: "Create a new checkout URL for a product variant. Returns a URL where the customer can complete their purchase. Supports custom pricing, prefilled customer data, and checkout customization.",
30946
31035
  annotations: {
30947
31036
  title: "Create checkout",
@@ -31002,6 +31091,7 @@ var checkoutTools = [
31002
31091
  var customerTools = [
31003
31092
  {
31004
31093
  name: "ls_get_customer",
31094
+ authorityClass: "pii",
31005
31095
  description: "Get a specific customer by ID, including name, email, city, country, MRR, total revenue, and customer portal URL.",
31006
31096
  annotations: {
31007
31097
  title: "Get customer",
@@ -31018,6 +31108,7 @@ var customerTools = [
31018
31108
  },
31019
31109
  {
31020
31110
  name: "ls_list_customers",
31111
+ authorityClass: "pii",
31021
31112
  description: "List all customers, optionally filtered by store or email. Results are paginated \u2014 check meta.page in the response for currentPage, lastPage, and total.",
31022
31113
  annotations: {
31023
31114
  title: "List customers",
@@ -31037,6 +31128,7 @@ var customerTools = [
31037
31128
  },
31038
31129
  {
31039
31130
  name: "ls_create_customer",
31131
+ authorityClass: "pii",
31040
31132
  description: "Create a new customer in a store.",
31041
31133
  annotations: {
31042
31134
  title: "Create customer",
@@ -31074,6 +31166,7 @@ var customerTools = [
31074
31166
  },
31075
31167
  {
31076
31168
  name: "ls_update_customer",
31169
+ authorityClass: "pii",
31077
31170
  description: "Update an existing customer's name, email, city, region, country, or status. The only supported status value is 'archived' \u2014 use ls_archive_customer for the dedicated, audit-tagged path.",
31078
31171
  annotations: {
31079
31172
  title: "Update customer",
@@ -31119,6 +31212,7 @@ var customerTools = [
31119
31212
  },
31120
31213
  {
31121
31214
  name: "ls_archive_customer",
31215
+ authorityClass: "pii",
31122
31216
  description: "Archive a customer. Sets their status to 'archived'. This is reversible by updating their status back.",
31123
31217
  annotations: {
31124
31218
  title: "Archive customer",
@@ -31146,6 +31240,7 @@ var customerTools = [
31146
31240
  var discountRedemptionTools = [
31147
31241
  {
31148
31242
  name: "ls_get_discount_redemption",
31243
+ authorityClass: "read",
31149
31244
  description: "Get a specific discount redemption by ID, showing when and where a discount was used.",
31150
31245
  annotations: {
31151
31246
  title: "Get discount redemption",
@@ -31162,6 +31257,7 @@ var discountRedemptionTools = [
31162
31257
  },
31163
31258
  {
31164
31259
  name: "ls_list_discount_redemptions",
31260
+ authorityClass: "read",
31165
31261
  description: "List all discount redemptions, optionally filtered by discount or order. Results are paginated \u2014 check meta.page in the response for currentPage, lastPage, and total. Cross-store note: when LEMONSQUEEZY_ALLOWED_STORE_IDS is set, this tool requires at least one of: discountId, orderId. Even with that set, pair with a scoped LemonSqueezy API key for true cross-store enforcement -- the API key's visibility is the true boundary.",
31166
31262
  annotations: {
31167
31263
  title: "List discount redemptions",
@@ -31186,6 +31282,7 @@ var discountRedemptionTools = [
31186
31282
  var discountTools = [
31187
31283
  {
31188
31284
  name: "ls_get_discount",
31285
+ authorityClass: "read",
31189
31286
  description: "Get a specific discount by ID, including code, amount, type, and usage limits.",
31190
31287
  annotations: {
31191
31288
  title: "Get discount",
@@ -31202,6 +31299,7 @@ var discountTools = [
31202
31299
  },
31203
31300
  {
31204
31301
  name: "ls_list_discounts",
31302
+ authorityClass: "read",
31205
31303
  description: "List all discounts, optionally filtered by store. Results are paginated \u2014 check meta.page in the response for currentPage, lastPage, and total.",
31206
31304
  annotations: {
31207
31305
  title: "List discounts",
@@ -31220,6 +31318,7 @@ var discountTools = [
31220
31318
  },
31221
31319
  {
31222
31320
  name: "ls_create_discount",
31321
+ authorityClass: "mutate",
31223
31322
  description: "Create a new discount code. Supports percentage or fixed amount discounts with optional duration and usage limits.",
31224
31323
  annotations: {
31225
31324
  title: "Create discount",
@@ -31278,6 +31377,7 @@ var discountTools = [
31278
31377
  },
31279
31378
  {
31280
31379
  name: "ls_delete_discount",
31380
+ authorityClass: "mutate",
31281
31381
  description: "Permanently delete a discount. This is irreversible.",
31282
31382
  annotations: {
31283
31383
  title: "Delete discount",
@@ -31299,6 +31399,7 @@ var discountTools = [
31299
31399
  var fileTools = [
31300
31400
  {
31301
31401
  name: "ls_get_file",
31402
+ authorityClass: "read",
31302
31403
  description: "Get a specific file by ID, including name, size, download URL, and associated variant.",
31303
31404
  annotations: {
31304
31405
  title: "Get file",
@@ -31315,6 +31416,7 @@ var fileTools = [
31315
31416
  },
31316
31417
  {
31317
31418
  name: "ls_list_files",
31419
+ authorityClass: "read",
31318
31420
  description: "List all files, optionally filtered by variant. Results are paginated \u2014 check meta.page in the response for currentPage, lastPage, and total. Cross-store note: when LEMONSQUEEZY_ALLOWED_STORE_IDS is set, this tool requires at least one of: variantId. Even with that set, pair with a scoped LemonSqueezy API key for true cross-store enforcement -- the API key's visibility is the true boundary.",
31319
31421
  annotations: {
31320
31422
  title: "List files",
@@ -31338,6 +31440,7 @@ var fileTools = [
31338
31440
  var licenseKeyInstanceTools = [
31339
31441
  {
31340
31442
  name: "ls_get_license_key_instance",
31443
+ authorityClass: "read",
31341
31444
  description: "Get a specific license key instance (activation) by ID, including instance name and creation date.",
31342
31445
  annotations: {
31343
31446
  title: "Get license key instance",
@@ -31354,6 +31457,7 @@ var licenseKeyInstanceTools = [
31354
31457
  },
31355
31458
  {
31356
31459
  name: "ls_list_license_key_instances",
31460
+ authorityClass: "read",
31357
31461
  description: "List all license key instances (activations), optionally filtered by license key. Results are paginated \u2014 check meta.page in the response for currentPage, lastPage, and total. Cross-store note: when LEMONSQUEEZY_ALLOWED_STORE_IDS is set, this tool requires at least one of: licenseKeyId. Even with that set, pair with a scoped LemonSqueezy API key for true cross-store enforcement -- the API key's visibility is the true boundary.",
31358
31462
  annotations: {
31359
31463
  title: "List license key instances",
@@ -31377,6 +31481,7 @@ var licenseKeyInstanceTools = [
31377
31481
  var licenseKeyTools = [
31378
31482
  {
31379
31483
  name: "ls_get_license_key",
31484
+ authorityClass: "read",
31380
31485
  description: "Get a specific license key by ID, including key value, status, activation limit, and expiry date.",
31381
31486
  annotations: {
31382
31487
  title: "Get license key",
@@ -31395,6 +31500,7 @@ var licenseKeyTools = [
31395
31500
  },
31396
31501
  {
31397
31502
  name: "ls_list_license_keys",
31503
+ authorityClass: "read",
31398
31504
  description: "List all license keys, optionally filtered by store, order, or product. Results are paginated \u2014 check meta.page in the response for currentPage, lastPage, and total.",
31399
31505
  annotations: {
31400
31506
  title: "List license keys",
@@ -31423,6 +31529,7 @@ var licenseKeyTools = [
31423
31529
  },
31424
31530
  {
31425
31531
  name: "ls_update_license_key",
31532
+ authorityClass: "key",
31426
31533
  description: "Update a license key's activation limit, expiry date, or disabled status. Setting `disabled: true` revokes customer access and is treated as destructive (rate-limited and audited).",
31427
31534
  annotations: {
31428
31535
  title: "Update license key",
@@ -31465,6 +31572,7 @@ var licenseKeyTools = [
31465
31572
  var licenseTools = [
31466
31573
  {
31467
31574
  name: "ls_activate_license",
31575
+ authorityClass: "key",
31468
31576
  description: "Activate a license key for an instance. Does not require an API key \u2014 uses the license key itself for auth.",
31469
31577
  annotations: {
31470
31578
  title: "Activate license",
@@ -31486,6 +31594,7 @@ var licenseTools = [
31486
31594
  },
31487
31595
  {
31488
31596
  name: "ls_validate_license",
31597
+ authorityClass: "read",
31489
31598
  description: "Validate a license key or specific instance. Does not require an API key \u2014 uses the license key itself for auth.",
31490
31599
  annotations: {
31491
31600
  title: "Validate license",
@@ -31506,6 +31615,7 @@ var licenseTools = [
31506
31615
  },
31507
31616
  {
31508
31617
  name: "ls_deactivate_license",
31618
+ authorityClass: "key",
31509
31619
  description: "Deactivate a license key instance. Does not require an API key \u2014 uses the license key itself for auth.",
31510
31620
  annotations: {
31511
31621
  title: "Deactivate license",
@@ -31531,6 +31641,7 @@ var licenseTools = [
31531
31641
  var orderItemTools = [
31532
31642
  {
31533
31643
  name: "ls_get_order_item",
31644
+ authorityClass: "read",
31534
31645
  description: "Get a specific order item by ID, including product name, variant, price, and quantity.",
31535
31646
  annotations: {
31536
31647
  title: "Get order item",
@@ -31547,6 +31658,7 @@ var orderItemTools = [
31547
31658
  },
31548
31659
  {
31549
31660
  name: "ls_list_order_items",
31661
+ authorityClass: "read",
31550
31662
  description: "List all order items, optionally filtered by order or product. Results are paginated \u2014 check meta.page in the response for currentPage, lastPage, and total. Cross-store note: when LEMONSQUEEZY_ALLOWED_STORE_IDS is set, this tool requires at least one of: orderId, productId, variantId. Even with that set, pair with a scoped LemonSqueezy API key for true cross-store enforcement -- the API key's visibility is the true boundary.",
31551
31663
  annotations: {
31552
31664
  title: "List order items",
@@ -31572,6 +31684,7 @@ var orderItemTools = [
31572
31684
  var orderTools = [
31573
31685
  {
31574
31686
  name: "ls_get_order",
31687
+ authorityClass: "read",
31575
31688
  description: "Get a specific order by ID, including status, total, currency, customer info, and payment details.",
31576
31689
  annotations: {
31577
31690
  title: "Get order",
@@ -31590,6 +31703,7 @@ var orderTools = [
31590
31703
  },
31591
31704
  {
31592
31705
  name: "ls_list_orders",
31706
+ authorityClass: "read",
31593
31707
  description: "List all orders, optionally filtered by store or user email. Results are paginated \u2014 check meta.page in the response for currentPage, lastPage, and total.",
31594
31708
  annotations: {
31595
31709
  title: "List orders",
@@ -31611,6 +31725,7 @@ var orderTools = [
31611
31725
  },
31612
31726
  {
31613
31727
  name: "ls_generate_order_invoice",
31728
+ authorityClass: "mutate",
31614
31729
  description: "Generate a PDF invoice for an order. Returns a download URL for the invoice.",
31615
31730
  annotations: {
31616
31731
  title: "Generate order invoice",
@@ -31646,6 +31761,7 @@ var orderTools = [
31646
31761
  },
31647
31762
  {
31648
31763
  name: "ls_refund_order",
31764
+ authorityClass: "money",
31649
31765
  description: "Issue a refund for an order. This is irreversible \u2014 the refund amount is in cents (e.g. 1000 = $10.00).",
31650
31766
  annotations: {
31651
31767
  title: "Refund order",
@@ -31671,6 +31787,7 @@ var orderTools = [
31671
31787
  var priceTools = [
31672
31788
  {
31673
31789
  name: "ls_get_price",
31790
+ authorityClass: "read",
31674
31791
  description: "Get a specific price by ID, including amount, currency, and billing interval.",
31675
31792
  annotations: {
31676
31793
  title: "Get price",
@@ -31687,6 +31804,7 @@ var priceTools = [
31687
31804
  },
31688
31805
  {
31689
31806
  name: "ls_list_prices",
31807
+ authorityClass: "read",
31690
31808
  description: "List all prices, optionally filtered by variant. Results are paginated \u2014 check meta.page in the response for currentPage, lastPage, and total. Cross-store note: when LEMONSQUEEZY_ALLOWED_STORE_IDS is set, this tool requires at least one of: variantId. Even with that set, pair with a scoped LemonSqueezy API key for true cross-store enforcement -- the API key's visibility is the true boundary.",
31691
31809
  annotations: {
31692
31810
  title: "List prices",
@@ -31710,6 +31828,7 @@ var priceTools = [
31710
31828
  var productTools = [
31711
31829
  {
31712
31830
  name: "ls_get_product",
31831
+ authorityClass: "read",
31713
31832
  description: "Get a specific product by ID, including name, description, price, and status.",
31714
31833
  annotations: {
31715
31834
  title: "Get product",
@@ -31726,6 +31845,7 @@ var productTools = [
31726
31845
  },
31727
31846
  {
31728
31847
  name: "ls_list_products",
31848
+ authorityClass: "read",
31729
31849
  description: "List all products, optionally filtered by store. Results are paginated \u2014 check meta.page in the response for currentPage, lastPage, and total.",
31730
31850
  annotations: {
31731
31851
  title: "List products",
@@ -31748,6 +31868,7 @@ var productTools = [
31748
31868
  var storeTools = [
31749
31869
  {
31750
31870
  name: "ls_get_store",
31871
+ authorityClass: "read",
31751
31872
  description: "Get a specific store by ID, including name, slug, currency, and sales statistics.",
31752
31873
  annotations: {
31753
31874
  title: "Get store",
@@ -31766,6 +31887,7 @@ var storeTools = [
31766
31887
  },
31767
31888
  {
31768
31889
  name: "ls_list_stores",
31890
+ authorityClass: "read",
31769
31891
  description: "List all stores for the authenticated user. Results are paginated \u2014 check meta.page in the response for currentPage, lastPage, and total.",
31770
31892
  annotations: {
31771
31893
  title: "List stores",
@@ -31789,6 +31911,7 @@ var storeTools = [
31789
31911
  var subscriptionInvoiceTools = [
31790
31912
  {
31791
31913
  name: "ls_get_subscription_invoice",
31914
+ authorityClass: "read",
31792
31915
  description: "Get a specific subscription invoice by ID, including status, total, billing reason, and payment details.",
31793
31916
  annotations: {
31794
31917
  title: "Get subscription invoice",
@@ -31805,6 +31928,7 @@ var subscriptionInvoiceTools = [
31805
31928
  },
31806
31929
  {
31807
31930
  name: "ls_list_subscription_invoices",
31931
+ authorityClass: "read",
31808
31932
  description: "List all subscription invoices, optionally filtered by store, subscription, or status. Results are paginated \u2014 check meta.page in the response for currentPage, lastPage, and total.",
31809
31933
  annotations: {
31810
31934
  title: "List subscription invoices",
@@ -31831,6 +31955,7 @@ var subscriptionInvoiceTools = [
31831
31955
  },
31832
31956
  {
31833
31957
  name: "ls_generate_subscription_invoice",
31958
+ authorityClass: "mutate",
31834
31959
  description: "Generate a PDF invoice for a subscription invoice. Returns a download URL.",
31835
31960
  annotations: {
31836
31961
  title: "Generate subscription invoice",
@@ -31868,6 +31993,7 @@ var subscriptionInvoiceTools = [
31868
31993
  },
31869
31994
  {
31870
31995
  name: "ls_refund_subscription_invoice",
31996
+ authorityClass: "money",
31871
31997
  description: "Issue a refund for a subscription invoice. This is irreversible \u2014 the refund amount is in cents (e.g. 1000 = $10.00).",
31872
31998
  annotations: {
31873
31999
  title: "Refund subscription invoice",
@@ -31897,6 +32023,7 @@ var subscriptionInvoiceTools = [
31897
32023
  var subscriptionItemTools = [
31898
32024
  {
31899
32025
  name: "ls_get_subscription_item",
32026
+ authorityClass: "read",
31900
32027
  description: "Get a specific subscription item by ID, including quantity, pricing, and associated subscription.",
31901
32028
  annotations: {
31902
32029
  title: "Get subscription item",
@@ -31913,6 +32040,7 @@ var subscriptionItemTools = [
31913
32040
  },
31914
32041
  {
31915
32042
  name: "ls_list_subscription_items",
32043
+ authorityClass: "read",
31916
32044
  description: "List all subscription items, optionally filtered by subscription or price. Results are paginated \u2014 check meta.page in the response for currentPage, lastPage, and total. Cross-store note: when LEMONSQUEEZY_ALLOWED_STORE_IDS is set, this tool requires at least one of: subscriptionId, priceId. Even with that set, pair with a scoped LemonSqueezy API key for true cross-store enforcement -- the API key's visibility is the true boundary.",
31917
32045
  annotations: {
31918
32046
  title: "List subscription items",
@@ -31933,6 +32061,7 @@ var subscriptionItemTools = [
31933
32061
  },
31934
32062
  {
31935
32063
  name: "ls_update_subscription_item",
32064
+ authorityClass: "recurring",
31936
32065
  description: "Update a subscription item's quantity. Used for seat-based or quantity-based billing.",
31937
32066
  annotations: {
31938
32067
  title: "Update subscription item",
@@ -31957,6 +32086,7 @@ var subscriptionItemTools = [
31957
32086
  },
31958
32087
  {
31959
32088
  name: "ls_get_subscription_item_usage",
32089
+ authorityClass: "read",
31960
32090
  description: "Get the current usage for a metered subscription item within the current billing period.",
31961
32091
  annotations: {
31962
32092
  title: "Get subscription item usage",
@@ -31978,6 +32108,7 @@ var subscriptionItemTools = [
31978
32108
  var subscriptionTools = [
31979
32109
  {
31980
32110
  name: "ls_get_subscription",
32111
+ authorityClass: "read",
31981
32112
  description: "Get a specific subscription by ID, including status, billing interval, renewal date, and customer info.",
31982
32113
  annotations: {
31983
32114
  title: "Get subscription",
@@ -31996,6 +32127,7 @@ var subscriptionTools = [
31996
32127
  },
31997
32128
  {
31998
32129
  name: "ls_list_subscriptions",
32130
+ authorityClass: "read",
31999
32131
  description: "List all subscriptions, optionally filtered by store, order, product, variant, or status. Results are paginated \u2014 check meta.page in the response for currentPage, lastPage, and total.",
32000
32132
  annotations: {
32001
32133
  title: "List subscriptions",
@@ -32030,6 +32162,7 @@ var subscriptionTools = [
32030
32162
  },
32031
32163
  {
32032
32164
  name: "ls_update_subscription",
32165
+ authorityClass: "recurring",
32033
32166
  description: "Update a subscription. Can change the variant (plan switch), pause/unpause, set billing anchor, or update invoice details. Use ls_cancel_subscription for cancellation.",
32034
32167
  annotations: {
32035
32168
  title: "Update subscription",
@@ -32081,6 +32214,7 @@ var subscriptionTools = [
32081
32214
  },
32082
32215
  {
32083
32216
  name: "ls_cancel_subscription",
32217
+ authorityClass: "recurring",
32084
32218
  description: "Cancel a subscription. The subscription remains active until the end of the current billing period, then expires.",
32085
32219
  annotations: {
32086
32220
  title: "Cancel subscription",
@@ -32102,6 +32236,7 @@ var subscriptionTools = [
32102
32236
  var usageRecordTools = [
32103
32237
  {
32104
32238
  name: "ls_get_usage_record",
32239
+ authorityClass: "read",
32105
32240
  description: "Get a specific usage record by ID, including quantity and action type.",
32106
32241
  annotations: {
32107
32242
  title: "Get usage record",
@@ -32118,6 +32253,7 @@ var usageRecordTools = [
32118
32253
  },
32119
32254
  {
32120
32255
  name: "ls_list_usage_records",
32256
+ authorityClass: "read",
32121
32257
  description: "List all usage records, optionally filtered by subscription item. Results are paginated \u2014 check meta.page in the response for currentPage, lastPage, and total. Cross-store note: when LEMONSQUEEZY_ALLOWED_STORE_IDS is set, this tool requires at least one of: subscriptionItemId. Even with that set, pair with a scoped LemonSqueezy API key for true cross-store enforcement -- the API key's visibility is the true boundary.",
32122
32258
  annotations: {
32123
32259
  title: "List usage records",
@@ -32137,6 +32273,7 @@ var usageRecordTools = [
32137
32273
  },
32138
32274
  {
32139
32275
  name: "ls_create_usage_record",
32276
+ authorityClass: "mutate",
32140
32277
  description: "Report usage for a metered subscription item. Use 'increment' action to add to the current usage, or 'set' to replace it.",
32141
32278
  annotations: {
32142
32279
  title: "Create usage record",
@@ -32177,6 +32314,7 @@ var usageRecordTools = [
32177
32314
  var userTools = [
32178
32315
  {
32179
32316
  name: "ls_get_user",
32317
+ authorityClass: "read",
32180
32318
  description: "Get the authenticated user's information including name, email, and avatar.",
32181
32319
  annotations: {
32182
32320
  title: "Get authenticated user",
@@ -32196,6 +32334,7 @@ var userTools = [
32196
32334
  var variantTools = [
32197
32335
  {
32198
32336
  name: "ls_get_variant",
32337
+ authorityClass: "read",
32199
32338
  description: "Get a specific product variant by ID, including price, billing interval, and trial settings.",
32200
32339
  annotations: {
32201
32340
  title: "Get variant",
@@ -32212,6 +32351,7 @@ var variantTools = [
32212
32351
  },
32213
32352
  {
32214
32353
  name: "ls_list_variants",
32354
+ authorityClass: "read",
32215
32355
  description: "List all variants, optionally filtered by product. Results are paginated \u2014 check meta.page in the response for currentPage, lastPage, and total. Cross-store note: when LEMONSQUEEZY_ALLOWED_STORE_IDS is set, this tool requires at least one of: productId. Even with that set, pair with a scoped LemonSqueezy API key for true cross-store enforcement -- the API key's visibility is the true boundary.",
32216
32356
  annotations: {
32217
32357
  title: "List variants",
@@ -32235,6 +32375,7 @@ var variantTools = [
32235
32375
  var webhookTools = [
32236
32376
  {
32237
32377
  name: "ls_get_webhook",
32378
+ authorityClass: "read",
32238
32379
  description: "Get a specific webhook by ID, including URL, events, and last sent timestamp.",
32239
32380
  annotations: {
32240
32381
  title: "Get webhook",
@@ -32251,6 +32392,7 @@ var webhookTools = [
32251
32392
  },
32252
32393
  {
32253
32394
  name: "ls_list_webhooks",
32395
+ authorityClass: "read",
32254
32396
  description: "List all webhooks, optionally filtered by store. Results are paginated \u2014 check meta.page in the response for currentPage, lastPage, and total.",
32255
32397
  annotations: {
32256
32398
  title: "List webhooks",
@@ -32269,6 +32411,7 @@ var webhookTools = [
32269
32411
  },
32270
32412
  {
32271
32413
  name: "ls_create_webhook",
32414
+ authorityClass: "webhook",
32272
32415
  description: "Create a new webhook to receive event notifications. The signing secret is returned only once \u2014 save it immediately.",
32273
32416
  annotations: {
32274
32417
  title: "Create webhook",
@@ -32303,6 +32446,7 @@ var webhookTools = [
32303
32446
  },
32304
32447
  {
32305
32448
  name: "ls_update_webhook",
32449
+ authorityClass: "webhook",
32306
32450
  description: "Update an existing webhook's URL, events, or secret.",
32307
32451
  annotations: {
32308
32452
  title: "Update webhook",
@@ -32333,6 +32477,7 @@ var webhookTools = [
32333
32477
  },
32334
32478
  {
32335
32479
  name: "ls_delete_webhook",
32480
+ authorityClass: "webhook",
32336
32481
  description: "Permanently delete a webhook. This is irreversible.",
32337
32482
  annotations: {
32338
32483
  title: "Delete webhook",
@@ -32351,7 +32496,7 @@ var webhookTools = [
32351
32496
  ];
32352
32497
 
32353
32498
  // src/index.ts
32354
- var version2 = true ? "0.8.1" : (await null).createRequire(import.meta.url)("../package.json").version;
32499
+ var version2 = true ? "0.9.0" : (await null).createRequire(import.meta.url)("../package.json").version;
32355
32500
  var subcommand = process.argv[2];
32356
32501
  if (subcommand === "version" || subcommand === "--version") {
32357
32502
  console.log(version2);
@@ -32394,6 +32539,8 @@ for (const tool of allTools) {
32394
32539
  const isDestructive = isDestructiveCall(tool, input);
32395
32540
  const start = Date.now();
32396
32541
  try {
32542
+ checkClassAllowed(tool.authorityClass);
32543
+ checkClassRateLimit(tool.authorityClass);
32397
32544
  if (isDestructive) checkDestructiveRateLimit();
32398
32545
  checkStoreScopedToolInput(tool, input);
32399
32546
  const result = await tool.handler(input);
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "@yawlabs/lemonsqueezy-mcp",
3
- "version": "0.8.1",
3
+ "version": "0.9.0",
4
+ "mcpName": "io.github.YawLabs/lemonsqueezy-mcp",
4
5
  "description": "LemonSqueezy MCP server for managing your store from AI assistants",
5
6
  "license": "MIT",
6
7
  "author": "YawLabs <contact@yaw.sh>",