@yawlabs/lemonsqueezy-mcp 0.7.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.
- package/README.md +104 -7
- package/dist/index.js +365 -61
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -2,12 +2,47 @@
|
|
|
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
|
+
[](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
|
|
8
12
|
npx @yawlabs/lemonsqueezy-mcp
|
|
9
13
|
```
|
|
10
14
|
|
|
15
|
+
Or one-click install via Smithery:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npx -y @smithery/cli install @yawlabs/lemonsqueezy-mcp --client claude
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Smithery prompts for your env vars (API key, optional guardrails) and writes the config into your client for you.
|
|
22
|
+
|
|
23
|
+
## What it looks like
|
|
24
|
+
|
|
25
|
+
Once configured, you can ask your AI assistant store-management questions in plain English and it routes them through the MCP tools:
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
You: How much did we make from the "Pro Annual" plan last month?
|
|
29
|
+
Claude: [calls ls_list_subscriptions, ls_get_variant, ls_list_subscription_invoices]
|
|
30
|
+
Pro Annual brought in $14,280 across 84 active subscriptions in April.
|
|
31
|
+
Three of those were upgrades from monthly; none churned.
|
|
32
|
+
|
|
33
|
+
You: Refund order #LS-1234 in full.
|
|
34
|
+
Claude: [calls ls_get_order to fetch the total, then ls_refund_order with amount = total]
|
|
35
|
+
Refunded $99.00 against order LS-1234. The customer's card will see the
|
|
36
|
+
credit in 5-10 business days.
|
|
37
|
+
|
|
38
|
+
You: Disable license key abc-123 for the customer who reported abuse.
|
|
39
|
+
Claude: [calls ls_list_license_keys to find the ID, then ls_update_license_key with disabled: true]
|
|
40
|
+
License key disabled. Their existing activations will fail validation
|
|
41
|
+
on the next check.
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Guardrails (refund cap, rate limit, store allowlist) catch the obvious mistakes before they reach LemonSqueezy. See [Configuration](#configuration) for the env vars that turn them on.
|
|
45
|
+
|
|
11
46
|
## Setup
|
|
12
47
|
|
|
13
48
|
Set your LemonSqueezy API key as an environment variable:
|
|
@@ -18,6 +53,17 @@ export LEMONSQUEEZY_API_KEY="your-api-key"
|
|
|
18
53
|
|
|
19
54
|
Get your API key from your [LemonSqueezy dashboard](https://app.lemonsqueezy.com/settings/api).
|
|
20
55
|
|
|
56
|
+
### Docker
|
|
57
|
+
|
|
58
|
+
A multi-stage `Dockerfile` is included at the repo root. The runtime image is a single bundled file on `node:20-alpine` running as the non-root `node` user, with no port exposed (stdio transport).
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
docker build -t yawlabs/lemonsqueezy-mcp .
|
|
62
|
+
docker run --rm -i -e LEMONSQUEEZY_API_KEY="your-api-key" yawlabs/lemonsqueezy-mcp
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
A byte-identical `Containerfile` is also provided for Podman users.
|
|
66
|
+
|
|
21
67
|
### Claude Code
|
|
22
68
|
|
|
23
69
|
Add to your MCP config:
|
|
@@ -174,9 +220,12 @@ All configuration is via environment variables. Only `LEMONSQUEEZY_API_KEY` (or
|
|
|
174
220
|
| --- | --- |
|
|
175
221
|
| `LEMONSQUEEZY_API_KEY` | LemonSqueezy API token. |
|
|
176
222
|
| `LEMONSQUEEZY_API_KEY_COMMAND` | Command whose stdout produces the API key. Overrides `LEMONSQUEEZY_API_KEY`. Output is cached for 1 hour. Use this to pull short-lived credentials from a vault (`op read`, `gcloud secrets versions access`, etc.) without writing them to env vars. The cache is keyed by the command string, so changing it mid-process refreshes on the next request; it is also invalidated automatically on a 401/403 from the API, so a key rotated upstream takes effect on the next call without waiting for the TTL. |
|
|
223
|
+
| `LEMONSQUEEZY_TEST_API_KEY` | Optional test-mode key. When set and non-empty, it takes precedence over `LEMONSQUEEZY_API_KEY` (but not over `LEMONSQUEEZY_API_KEY_COMMAND`). On first activation per process, the server prints a one-line JSON `test_mode` notice to stderr so you can confirm test mode is engaged. Use this to point the server at a sandbox/test store without unsetting your production key. |
|
|
177
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. |
|
|
178
225
|
| `LEMONSQUEEZY_MAX_REFUND_AMOUNT_CENTS` | Rejects `ls_refund_order` and `ls_refund_subscription_invoice` calls above this amount. |
|
|
179
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. |
|
|
180
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. |
|
|
181
230
|
|
|
182
231
|
### Logging format
|
|
@@ -187,15 +236,42 @@ Each line: `{ts, event, tool?, method?, path?, status, latency_ms, request_id?,
|
|
|
187
236
|
|
|
188
237
|
HTTP errors include the upstream `X-Request-Id` when present, so support tickets to LemonSqueezy can reference the exact call.
|
|
189
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
|
+
|
|
257
|
+
## Resources
|
|
258
|
+
|
|
259
|
+
The server exposes one MCP Resource for clients that prefer structural retrieval over parsing stderr:
|
|
260
|
+
|
|
261
|
+
| URI | MIME type | Contents |
|
|
262
|
+
| --- | --- | --- |
|
|
263
|
+
| `lemonsqueezy://audit-log` | `application/x-ndjson` | The most recent destructive tool calls and outcomes (rate-limit blocks, refund-cap blocks, exceptions, successes). Bounded ring buffer of the last 1000 entries, most-recent-first, resets on server restart. Secret-shaped input fields are redacted before they reach the buffer. |
|
|
264
|
+
|
|
190
265
|
## Operating the server unattended
|
|
191
266
|
|
|
192
267
|
For unattended/agentic use against a live store, we recommend:
|
|
193
268
|
|
|
194
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`.
|
|
195
270
|
2. Set `LEMONSQUEEZY_MAX_REFUND_AMOUNT_CENTS` to a per-call cap well below any single-refund expectation.
|
|
196
|
-
3. Set `LEMONSQUEEZY_DESTRUCTIVE_RATE_LIMIT` to a small number (e.g. 5/min) as a runaway-agent circuit breaker.
|
|
197
|
-
4.
|
|
198
|
-
5.
|
|
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.
|
|
199
275
|
|
|
200
276
|
What the server does **not** do and you must own at the caller level:
|
|
201
277
|
|
|
@@ -216,15 +292,36 @@ npm run test:integration # requires LEMONSQUEEZY_TEST_API_KEY + LEMONSQUEEZY_TE
|
|
|
216
292
|
|
|
217
293
|
## Releasing
|
|
218
294
|
|
|
219
|
-
|
|
295
|
+
Two paths from a clean checkout of `main`. Both produce the same artifact (npm publish with provenance + GitHub release).
|
|
296
|
+
|
|
297
|
+
### 1. Tag-and-let-CI (preferred)
|
|
220
298
|
|
|
221
299
|
```bash
|
|
222
|
-
|
|
300
|
+
# 1. Bump version
|
|
301
|
+
npm version X.Y.Z --no-git-tag-version
|
|
302
|
+
|
|
303
|
+
# 2. Commit
|
|
304
|
+
git add package.json && git commit -m "vX.Y.Z"
|
|
305
|
+
|
|
306
|
+
# 3. Annotated tag (lightweight tags are silently skipped by --follow-tags)
|
|
307
|
+
git tag -a vX.Y.Z -m "vX.Y.Z"
|
|
308
|
+
|
|
309
|
+
# 4. Push commit + tag
|
|
310
|
+
git push origin main --follow-tags
|
|
311
|
+
|
|
312
|
+
# 5. Confirm the Release workflow fired (not just CI on the bump commit)
|
|
313
|
+
gh run list --limit 2
|
|
223
314
|
```
|
|
224
315
|
|
|
225
|
-
The
|
|
316
|
+
The tag push triggers `.github/workflows/release.yml`, which runs `release.sh` in CI mode: lint, test, build, npm publish (with `--provenance`) using the org-level `NPM_TOKEN` secret, then GitHub release creation, then a smoke test against the published tarball. No local `npm login` needed.
|
|
317
|
+
|
|
318
|
+
### 2. Local end-to-end
|
|
319
|
+
|
|
320
|
+
```bash
|
|
321
|
+
./release.sh X.Y.Z
|
|
322
|
+
```
|
|
226
323
|
|
|
227
|
-
|
|
324
|
+
Does the same steps 1–7 on the workstation: lint, test, build, bump, commit, annotated tag, push, npm publish, GitHub release, verify. Idempotent — safe to re-run with the same version after a partial failure. Requires one-time setup:
|
|
228
325
|
|
|
229
326
|
```bash
|
|
230
327
|
npm login --auth-type=web # publisher of @yawlabs/lemonsqueezy-mcp
|
package/dist/index.js
CHANGED
|
@@ -3105,6 +3105,9 @@ var require_utils = __commonJS({
|
|
|
3105
3105
|
"use strict";
|
|
3106
3106
|
var isUUID = RegExp.prototype.test.bind(/^[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}$/iu);
|
|
3107
3107
|
var isIPv4 = RegExp.prototype.test.bind(/^(?:(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]\d|\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]\d|\d)$/u);
|
|
3108
|
+
var isHexPair = RegExp.prototype.test.bind(/^[\da-f]{2}$/iu);
|
|
3109
|
+
var isUnreserved = RegExp.prototype.test.bind(/^[\da-z\-._~]$/iu);
|
|
3110
|
+
var isPathCharacter = RegExp.prototype.test.bind(/^[\da-z\-._~!$&'()*+,;=:@/]$/iu);
|
|
3108
3111
|
function stringArrayToHexStripped(input) {
|
|
3109
3112
|
let acc = "";
|
|
3110
3113
|
let code = 0;
|
|
@@ -3130,20 +3133,20 @@ var require_utils = __commonJS({
|
|
|
3130
3133
|
return acc;
|
|
3131
3134
|
}
|
|
3132
3135
|
var nonSimpleDomain = RegExp.prototype.test.bind(/[^!"$&'()*+,\-.;=_`a-z{}~]/u);
|
|
3133
|
-
function consumeIsZone(
|
|
3134
|
-
|
|
3136
|
+
function consumeIsZone(buffer2) {
|
|
3137
|
+
buffer2.length = 0;
|
|
3135
3138
|
return true;
|
|
3136
3139
|
}
|
|
3137
|
-
function consumeHextets(
|
|
3138
|
-
if (
|
|
3139
|
-
const hex3 = stringArrayToHexStripped(
|
|
3140
|
+
function consumeHextets(buffer2, address, output) {
|
|
3141
|
+
if (buffer2.length) {
|
|
3142
|
+
const hex3 = stringArrayToHexStripped(buffer2);
|
|
3140
3143
|
if (hex3 !== "") {
|
|
3141
3144
|
address.push(hex3);
|
|
3142
3145
|
} else {
|
|
3143
3146
|
output.error = true;
|
|
3144
3147
|
return false;
|
|
3145
3148
|
}
|
|
3146
|
-
|
|
3149
|
+
buffer2.length = 0;
|
|
3147
3150
|
}
|
|
3148
3151
|
return true;
|
|
3149
3152
|
}
|
|
@@ -3151,7 +3154,7 @@ var require_utils = __commonJS({
|
|
|
3151
3154
|
let tokenCount = 0;
|
|
3152
3155
|
const output = { error: false, address: "", zone: "" };
|
|
3153
3156
|
const address = [];
|
|
3154
|
-
const
|
|
3157
|
+
const buffer2 = [];
|
|
3155
3158
|
let endipv6Encountered = false;
|
|
3156
3159
|
let endIpv6 = false;
|
|
3157
3160
|
let consume = consumeHextets;
|
|
@@ -3164,7 +3167,7 @@ var require_utils = __commonJS({
|
|
|
3164
3167
|
if (endipv6Encountered === true) {
|
|
3165
3168
|
endIpv6 = true;
|
|
3166
3169
|
}
|
|
3167
|
-
if (!consume(
|
|
3170
|
+
if (!consume(buffer2, address, output)) {
|
|
3168
3171
|
break;
|
|
3169
3172
|
}
|
|
3170
3173
|
if (++tokenCount > 7) {
|
|
@@ -3177,22 +3180,22 @@ var require_utils = __commonJS({
|
|
|
3177
3180
|
address.push(":");
|
|
3178
3181
|
continue;
|
|
3179
3182
|
} else if (cursor === "%") {
|
|
3180
|
-
if (!consume(
|
|
3183
|
+
if (!consume(buffer2, address, output)) {
|
|
3181
3184
|
break;
|
|
3182
3185
|
}
|
|
3183
3186
|
consume = consumeIsZone;
|
|
3184
3187
|
} else {
|
|
3185
|
-
|
|
3188
|
+
buffer2.push(cursor);
|
|
3186
3189
|
continue;
|
|
3187
3190
|
}
|
|
3188
3191
|
}
|
|
3189
|
-
if (
|
|
3192
|
+
if (buffer2.length) {
|
|
3190
3193
|
if (consume === consumeIsZone) {
|
|
3191
|
-
output.zone =
|
|
3194
|
+
output.zone = buffer2.join("");
|
|
3192
3195
|
} else if (endIpv6) {
|
|
3193
|
-
address.push(
|
|
3196
|
+
address.push(buffer2.join(""));
|
|
3194
3197
|
} else {
|
|
3195
|
-
address.push(stringArrayToHexStripped(
|
|
3198
|
+
address.push(stringArrayToHexStripped(buffer2));
|
|
3196
3199
|
}
|
|
3197
3200
|
}
|
|
3198
3201
|
output.address = address.join("");
|
|
@@ -3297,27 +3300,77 @@ var require_utils = __commonJS({
|
|
|
3297
3300
|
}
|
|
3298
3301
|
return output.join("");
|
|
3299
3302
|
}
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
3303
|
+
var HOST_DELIMS = { "@": "%40", "/": "%2F", "?": "%3F", "#": "%23", ":": "%3A" };
|
|
3304
|
+
var HOST_DELIM_RE = /[@/?#:]/g;
|
|
3305
|
+
var HOST_DELIM_NO_COLON_RE = /[@/?#]/g;
|
|
3306
|
+
function reescapeHostDelimiters(host, isIP) {
|
|
3307
|
+
const re = isIP ? HOST_DELIM_NO_COLON_RE : HOST_DELIM_RE;
|
|
3308
|
+
re.lastIndex = 0;
|
|
3309
|
+
return host.replace(re, (ch) => HOST_DELIMS[ch]);
|
|
3310
|
+
}
|
|
3311
|
+
function normalizePercentEncoding(input, decodeUnreserved = false) {
|
|
3312
|
+
if (input.indexOf("%") === -1) {
|
|
3313
|
+
return input;
|
|
3310
3314
|
}
|
|
3311
|
-
|
|
3312
|
-
|
|
3315
|
+
let output = "";
|
|
3316
|
+
for (let i = 0; i < input.length; i++) {
|
|
3317
|
+
if (input[i] === "%" && i + 2 < input.length) {
|
|
3318
|
+
const hex3 = input.slice(i + 1, i + 3);
|
|
3319
|
+
if (isHexPair(hex3)) {
|
|
3320
|
+
const normalizedHex = hex3.toUpperCase();
|
|
3321
|
+
const decoded = String.fromCharCode(parseInt(normalizedHex, 16));
|
|
3322
|
+
if (decodeUnreserved && isUnreserved(decoded)) {
|
|
3323
|
+
output += decoded;
|
|
3324
|
+
} else {
|
|
3325
|
+
output += "%" + normalizedHex;
|
|
3326
|
+
}
|
|
3327
|
+
i += 2;
|
|
3328
|
+
continue;
|
|
3329
|
+
}
|
|
3330
|
+
}
|
|
3331
|
+
output += input[i];
|
|
3313
3332
|
}
|
|
3314
|
-
|
|
3315
|
-
|
|
3333
|
+
return output;
|
|
3334
|
+
}
|
|
3335
|
+
function normalizePathEncoding(input) {
|
|
3336
|
+
let output = "";
|
|
3337
|
+
for (let i = 0; i < input.length; i++) {
|
|
3338
|
+
if (input[i] === "%" && i + 2 < input.length) {
|
|
3339
|
+
const hex3 = input.slice(i + 1, i + 3);
|
|
3340
|
+
if (isHexPair(hex3)) {
|
|
3341
|
+
const normalizedHex = hex3.toUpperCase();
|
|
3342
|
+
const decoded = String.fromCharCode(parseInt(normalizedHex, 16));
|
|
3343
|
+
if (decoded !== "." && isUnreserved(decoded)) {
|
|
3344
|
+
output += decoded;
|
|
3345
|
+
} else {
|
|
3346
|
+
output += "%" + normalizedHex;
|
|
3347
|
+
}
|
|
3348
|
+
i += 2;
|
|
3349
|
+
continue;
|
|
3350
|
+
}
|
|
3351
|
+
}
|
|
3352
|
+
if (isPathCharacter(input[i])) {
|
|
3353
|
+
output += input[i];
|
|
3354
|
+
} else {
|
|
3355
|
+
output += escape(input[i]);
|
|
3356
|
+
}
|
|
3316
3357
|
}
|
|
3317
|
-
|
|
3318
|
-
|
|
3358
|
+
return output;
|
|
3359
|
+
}
|
|
3360
|
+
function escapePreservingEscapes(input) {
|
|
3361
|
+
let output = "";
|
|
3362
|
+
for (let i = 0; i < input.length; i++) {
|
|
3363
|
+
if (input[i] === "%" && i + 2 < input.length) {
|
|
3364
|
+
const hex3 = input.slice(i + 1, i + 3);
|
|
3365
|
+
if (isHexPair(hex3)) {
|
|
3366
|
+
output += "%" + hex3.toUpperCase();
|
|
3367
|
+
i += 2;
|
|
3368
|
+
continue;
|
|
3369
|
+
}
|
|
3370
|
+
}
|
|
3371
|
+
output += escape(input[i]);
|
|
3319
3372
|
}
|
|
3320
|
-
return
|
|
3373
|
+
return output;
|
|
3321
3374
|
}
|
|
3322
3375
|
function recomposeAuthority(component) {
|
|
3323
3376
|
const uriTokens = [];
|
|
@@ -3332,7 +3385,7 @@ var require_utils = __commonJS({
|
|
|
3332
3385
|
if (ipV6res.isIPV6 === true) {
|
|
3333
3386
|
host = `[${ipV6res.escapedHost}]`;
|
|
3334
3387
|
} else {
|
|
3335
|
-
host =
|
|
3388
|
+
host = reescapeHostDelimiters(host, false);
|
|
3336
3389
|
}
|
|
3337
3390
|
}
|
|
3338
3391
|
uriTokens.push(host);
|
|
@@ -3346,7 +3399,10 @@ var require_utils = __commonJS({
|
|
|
3346
3399
|
module.exports = {
|
|
3347
3400
|
nonSimpleDomain,
|
|
3348
3401
|
recomposeAuthority,
|
|
3349
|
-
|
|
3402
|
+
reescapeHostDelimiters,
|
|
3403
|
+
normalizePercentEncoding,
|
|
3404
|
+
normalizePathEncoding,
|
|
3405
|
+
escapePreservingEscapes,
|
|
3350
3406
|
removeDotSegments,
|
|
3351
3407
|
isIPv4,
|
|
3352
3408
|
isUUID,
|
|
@@ -3570,12 +3626,12 @@ var require_schemes = __commonJS({
|
|
|
3570
3626
|
var require_fast_uri = __commonJS({
|
|
3571
3627
|
"node_modules/fast-uri/index.js"(exports, module) {
|
|
3572
3628
|
"use strict";
|
|
3573
|
-
var { normalizeIPv6, removeDotSegments, recomposeAuthority,
|
|
3629
|
+
var { normalizeIPv6, removeDotSegments, recomposeAuthority, normalizePercentEncoding, normalizePathEncoding, escapePreservingEscapes, reescapeHostDelimiters, isIPv4, nonSimpleDomain } = require_utils();
|
|
3574
3630
|
var { SCHEMES, getSchemeHandler } = require_schemes();
|
|
3575
3631
|
function normalize(uri, options) {
|
|
3576
3632
|
if (typeof uri === "string") {
|
|
3577
3633
|
uri = /** @type {T} */
|
|
3578
|
-
|
|
3634
|
+
normalizeString(uri, options);
|
|
3579
3635
|
} else if (typeof uri === "object") {
|
|
3580
3636
|
uri = /** @type {T} */
|
|
3581
3637
|
parse3(serialize(uri, options), options);
|
|
@@ -3642,19 +3698,9 @@ var require_fast_uri = __commonJS({
|
|
|
3642
3698
|
return target;
|
|
3643
3699
|
}
|
|
3644
3700
|
function equal(uriA, uriB, options) {
|
|
3645
|
-
|
|
3646
|
-
|
|
3647
|
-
|
|
3648
|
-
} else if (typeof uriA === "object") {
|
|
3649
|
-
uriA = serialize(normalizeComponentEncoding(uriA, true), { ...options, skipEscape: true });
|
|
3650
|
-
}
|
|
3651
|
-
if (typeof uriB === "string") {
|
|
3652
|
-
uriB = unescape(uriB);
|
|
3653
|
-
uriB = serialize(normalizeComponentEncoding(parse3(uriB, options), true), { ...options, skipEscape: true });
|
|
3654
|
-
} else if (typeof uriB === "object") {
|
|
3655
|
-
uriB = serialize(normalizeComponentEncoding(uriB, true), { ...options, skipEscape: true });
|
|
3656
|
-
}
|
|
3657
|
-
return uriA.toLowerCase() === uriB.toLowerCase();
|
|
3701
|
+
const normalizedA = normalizeComparableURI(uriA, options);
|
|
3702
|
+
const normalizedB = normalizeComparableURI(uriB, options);
|
|
3703
|
+
return normalizedA !== void 0 && normalizedB !== void 0 && normalizedA.toLowerCase() === normalizedB.toLowerCase();
|
|
3658
3704
|
}
|
|
3659
3705
|
function serialize(cmpts, opts) {
|
|
3660
3706
|
const component = {
|
|
@@ -3679,12 +3725,12 @@ var require_fast_uri = __commonJS({
|
|
|
3679
3725
|
if (schemeHandler && schemeHandler.serialize) schemeHandler.serialize(component, options);
|
|
3680
3726
|
if (component.path !== void 0) {
|
|
3681
3727
|
if (!options.skipEscape) {
|
|
3682
|
-
component.path =
|
|
3728
|
+
component.path = escapePreservingEscapes(component.path);
|
|
3683
3729
|
if (component.scheme !== void 0) {
|
|
3684
3730
|
component.path = component.path.split("%3A").join(":");
|
|
3685
3731
|
}
|
|
3686
3732
|
} else {
|
|
3687
|
-
component.path =
|
|
3733
|
+
component.path = normalizePercentEncoding(component.path);
|
|
3688
3734
|
}
|
|
3689
3735
|
}
|
|
3690
3736
|
if (options.reference !== "suffix" && component.scheme) {
|
|
@@ -3719,7 +3765,16 @@ var require_fast_uri = __commonJS({
|
|
|
3719
3765
|
return uriTokens.join("");
|
|
3720
3766
|
}
|
|
3721
3767
|
var URI_PARSE = /^(?:([^#/:?]+):)?(?:\/\/((?:([^#/?@]*)@)?(\[[^#/?\]]+\]|[^#/:?]*)(?::(\d*))?))?([^#?]*)(?:\?([^#]*))?(?:#((?:.|[\n\r])*))?/u;
|
|
3722
|
-
function
|
|
3768
|
+
function getParseError(parsed, matches) {
|
|
3769
|
+
if (matches[2] !== void 0 && parsed.path && parsed.path[0] !== "/") {
|
|
3770
|
+
return 'URI path must start with "/" when authority is present.';
|
|
3771
|
+
}
|
|
3772
|
+
if (typeof parsed.port === "number" && (parsed.port < 0 || parsed.port > 65535)) {
|
|
3773
|
+
return "URI port is malformed.";
|
|
3774
|
+
}
|
|
3775
|
+
return void 0;
|
|
3776
|
+
}
|
|
3777
|
+
function parseWithStatus(uri, opts) {
|
|
3723
3778
|
const options = Object.assign({}, opts);
|
|
3724
3779
|
const parsed = {
|
|
3725
3780
|
scheme: void 0,
|
|
@@ -3730,6 +3785,7 @@ var require_fast_uri = __commonJS({
|
|
|
3730
3785
|
query: void 0,
|
|
3731
3786
|
fragment: void 0
|
|
3732
3787
|
};
|
|
3788
|
+
let malformedAuthorityOrPort = false;
|
|
3733
3789
|
let isIP = false;
|
|
3734
3790
|
if (options.reference === "suffix") {
|
|
3735
3791
|
if (options.scheme) {
|
|
@@ -3750,6 +3806,11 @@ var require_fast_uri = __commonJS({
|
|
|
3750
3806
|
if (isNaN(parsed.port)) {
|
|
3751
3807
|
parsed.port = matches[5];
|
|
3752
3808
|
}
|
|
3809
|
+
const parseError = getParseError(parsed, matches);
|
|
3810
|
+
if (parseError !== void 0) {
|
|
3811
|
+
parsed.error = parsed.error || parseError;
|
|
3812
|
+
malformedAuthorityOrPort = true;
|
|
3813
|
+
}
|
|
3753
3814
|
if (parsed.host) {
|
|
3754
3815
|
const ipv4result = isIPv4(parsed.host);
|
|
3755
3816
|
if (ipv4result === false) {
|
|
@@ -3788,14 +3849,18 @@ var require_fast_uri = __commonJS({
|
|
|
3788
3849
|
parsed.scheme = unescape(parsed.scheme);
|
|
3789
3850
|
}
|
|
3790
3851
|
if (parsed.host !== void 0) {
|
|
3791
|
-
parsed.host = unescape(parsed.host);
|
|
3852
|
+
parsed.host = reescapeHostDelimiters(unescape(parsed.host), isIP);
|
|
3792
3853
|
}
|
|
3793
3854
|
}
|
|
3794
3855
|
if (parsed.path) {
|
|
3795
|
-
parsed.path =
|
|
3856
|
+
parsed.path = normalizePathEncoding(parsed.path);
|
|
3796
3857
|
}
|
|
3797
3858
|
if (parsed.fragment) {
|
|
3798
|
-
|
|
3859
|
+
try {
|
|
3860
|
+
parsed.fragment = encodeURI(decodeURIComponent(parsed.fragment));
|
|
3861
|
+
} catch {
|
|
3862
|
+
parsed.error = parsed.error || "URI malformed";
|
|
3863
|
+
}
|
|
3799
3864
|
}
|
|
3800
3865
|
}
|
|
3801
3866
|
if (schemeHandler && schemeHandler.parse) {
|
|
@@ -3804,7 +3869,29 @@ var require_fast_uri = __commonJS({
|
|
|
3804
3869
|
} else {
|
|
3805
3870
|
parsed.error = parsed.error || "URI can not be parsed.";
|
|
3806
3871
|
}
|
|
3807
|
-
return parsed;
|
|
3872
|
+
return { parsed, malformedAuthorityOrPort };
|
|
3873
|
+
}
|
|
3874
|
+
function parse3(uri, opts) {
|
|
3875
|
+
return parseWithStatus(uri, opts).parsed;
|
|
3876
|
+
}
|
|
3877
|
+
function normalizeString(uri, opts) {
|
|
3878
|
+
return normalizeStringWithStatus(uri, opts).normalized;
|
|
3879
|
+
}
|
|
3880
|
+
function normalizeStringWithStatus(uri, opts) {
|
|
3881
|
+
const { parsed, malformedAuthorityOrPort } = parseWithStatus(uri, opts);
|
|
3882
|
+
return {
|
|
3883
|
+
normalized: malformedAuthorityOrPort ? uri : serialize(parsed, opts),
|
|
3884
|
+
malformedAuthorityOrPort
|
|
3885
|
+
};
|
|
3886
|
+
}
|
|
3887
|
+
function normalizeComparableURI(uri, opts) {
|
|
3888
|
+
if (typeof uri === "string") {
|
|
3889
|
+
const { normalized, malformedAuthorityOrPort } = normalizeStringWithStatus(uri, opts);
|
|
3890
|
+
return malformedAuthorityOrPort ? void 0 : normalized;
|
|
3891
|
+
}
|
|
3892
|
+
if (typeof uri === "object") {
|
|
3893
|
+
return serialize(uri, opts);
|
|
3894
|
+
}
|
|
3808
3895
|
}
|
|
3809
3896
|
var fastUri = {
|
|
3810
3897
|
SCHEMES,
|
|
@@ -30113,6 +30200,23 @@ var StdioServerTransport = class {
|
|
|
30113
30200
|
}
|
|
30114
30201
|
};
|
|
30115
30202
|
|
|
30203
|
+
// src/audit-buffer.ts
|
|
30204
|
+
var DEFAULT_CAP = 1e3;
|
|
30205
|
+
var buffer = [];
|
|
30206
|
+
var cap = DEFAULT_CAP;
|
|
30207
|
+
function pushAuditEntry(entry) {
|
|
30208
|
+
buffer.push(entry);
|
|
30209
|
+
if (buffer.length > cap) {
|
|
30210
|
+
buffer.splice(0, buffer.length - cap);
|
|
30211
|
+
}
|
|
30212
|
+
}
|
|
30213
|
+
function readAuditEntries(limit) {
|
|
30214
|
+
const reversed = buffer.slice().reverse();
|
|
30215
|
+
if (limit === void 0 || limit >= reversed.length) return reversed;
|
|
30216
|
+
if (limit <= 0) return [];
|
|
30217
|
+
return reversed.slice(0, limit);
|
|
30218
|
+
}
|
|
30219
|
+
|
|
30116
30220
|
// src/guardrails.ts
|
|
30117
30221
|
var GuardrailError = class extends Error {
|
|
30118
30222
|
constructor(message) {
|
|
@@ -30120,8 +30224,13 @@ var GuardrailError = class extends Error {
|
|
|
30120
30224
|
this.name = "GuardrailError";
|
|
30121
30225
|
}
|
|
30122
30226
|
};
|
|
30227
|
+
var AUTHORITY_CLASSES = ["read", "pii", "mutate", "money", "recurring", "key", "webhook"];
|
|
30228
|
+
function isAuthorityClass(s) {
|
|
30229
|
+
return AUTHORITY_CLASSES.includes(s);
|
|
30230
|
+
}
|
|
30123
30231
|
var cachedOptions = null;
|
|
30124
30232
|
var destructiveTimestamps = [];
|
|
30233
|
+
var classTimestamps = /* @__PURE__ */ new Map();
|
|
30125
30234
|
function readNumber(name, raw) {
|
|
30126
30235
|
if (raw === void 0 || raw.trim() === "") return null;
|
|
30127
30236
|
const n = Number(raw);
|
|
@@ -30130,6 +30239,59 @@ function readNumber(name, raw) {
|
|
|
30130
30239
|
}
|
|
30131
30240
|
return n;
|
|
30132
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
|
+
}
|
|
30133
30295
|
function loadOptions() {
|
|
30134
30296
|
if (cachedOptions) return cachedOptions;
|
|
30135
30297
|
const allowed = process.env.LEMONSQUEEZY_ALLOWED_STORE_IDS;
|
|
@@ -30145,7 +30307,9 @@ function loadOptions() {
|
|
|
30145
30307
|
rateLimitPerMinute: readNumber(
|
|
30146
30308
|
"LEMONSQUEEZY_DESTRUCTIVE_RATE_LIMIT",
|
|
30147
30309
|
process.env.LEMONSQUEEZY_DESTRUCTIVE_RATE_LIMIT
|
|
30148
|
-
)
|
|
30310
|
+
),
|
|
30311
|
+
disabledClasses: parseDisabledClasses(process.env.LEMONSQUEEZY_DISABLE_CLASSES),
|
|
30312
|
+
classRateLimits: parseClassRateLimits(process.env.LEMONSQUEEZY_RATE_LIMIT_PER_CLASS)
|
|
30149
30313
|
};
|
|
30150
30314
|
return cachedOptions;
|
|
30151
30315
|
}
|
|
@@ -30208,6 +30372,30 @@ function isDestructiveCall(tool, input) {
|
|
|
30208
30372
|
if (typeof tool.isDestructive === "function") return tool.isDestructive(input);
|
|
30209
30373
|
return tool.annotations?.destructiveHint === true;
|
|
30210
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
|
+
}
|
|
30211
30399
|
|
|
30212
30400
|
// src/logger.ts
|
|
30213
30401
|
function getLogLevel() {
|
|
@@ -30413,6 +30601,18 @@ var CACHE_TTL_MS = 60 * 60 * 1e3;
|
|
|
30413
30601
|
var COMMAND_TIMEOUT_MS = 1e4;
|
|
30414
30602
|
var COMMAND_MAX_BUFFER = 64 * 1024;
|
|
30415
30603
|
var cached2 = null;
|
|
30604
|
+
var testModeAnnounced = false;
|
|
30605
|
+
function announceTestModeOnce() {
|
|
30606
|
+
if (testModeAnnounced) return;
|
|
30607
|
+
testModeAnnounced = true;
|
|
30608
|
+
const line = `${JSON.stringify({
|
|
30609
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
30610
|
+
event: "test_mode",
|
|
30611
|
+
message: "Using LEMONSQUEEZY_TEST_API_KEY (test mode)"
|
|
30612
|
+
})}
|
|
30613
|
+
`;
|
|
30614
|
+
process.stderr.write(line);
|
|
30615
|
+
}
|
|
30416
30616
|
function parseCommand(cmd) {
|
|
30417
30617
|
const parts = [];
|
|
30418
30618
|
let current = "";
|
|
@@ -30472,6 +30672,18 @@ async function loadApiKey() {
|
|
|
30472
30672
|
intoCache(fingerprint2, key);
|
|
30473
30673
|
return key;
|
|
30474
30674
|
}
|
|
30675
|
+
const testRaw = process.env.LEMONSQUEEZY_TEST_API_KEY;
|
|
30676
|
+
if (testRaw && testRaw.trim() !== "") {
|
|
30677
|
+
const fingerprint2 = `test:${testRaw}`;
|
|
30678
|
+
const hit2 = fromCache(fingerprint2);
|
|
30679
|
+
if (hit2 !== null) {
|
|
30680
|
+
announceTestModeOnce();
|
|
30681
|
+
return hit2;
|
|
30682
|
+
}
|
|
30683
|
+
announceTestModeOnce();
|
|
30684
|
+
intoCache(fingerprint2, testRaw);
|
|
30685
|
+
return testRaw;
|
|
30686
|
+
}
|
|
30475
30687
|
const raw = process.env.LEMONSQUEEZY_API_KEY;
|
|
30476
30688
|
if (!raw) {
|
|
30477
30689
|
throw new Error("LEMONSQUEEZY_API_KEY or LEMONSQUEEZY_API_KEY_COMMAND environment variable is required.");
|
|
@@ -30741,6 +30953,7 @@ async function apiDelete(path) {
|
|
|
30741
30953
|
var affiliateTools = [
|
|
30742
30954
|
{
|
|
30743
30955
|
name: "ls_get_affiliate",
|
|
30956
|
+
authorityClass: "read",
|
|
30744
30957
|
description: "Get a specific affiliate by ID, including commission rate, status, and earnings.",
|
|
30745
30958
|
annotations: {
|
|
30746
30959
|
title: "Get affiliate",
|
|
@@ -30757,6 +30970,7 @@ var affiliateTools = [
|
|
|
30757
30970
|
},
|
|
30758
30971
|
{
|
|
30759
30972
|
name: "ls_list_affiliates",
|
|
30973
|
+
authorityClass: "read",
|
|
30760
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.",
|
|
30761
30975
|
annotations: {
|
|
30762
30976
|
title: "List affiliates",
|
|
@@ -30779,6 +30993,7 @@ var affiliateTools = [
|
|
|
30779
30993
|
var checkoutTools = [
|
|
30780
30994
|
{
|
|
30781
30995
|
name: "ls_get_checkout",
|
|
30996
|
+
authorityClass: "read",
|
|
30782
30997
|
description: "Get a specific checkout by ID, including URL, expiry, and custom data.",
|
|
30783
30998
|
annotations: {
|
|
30784
30999
|
title: "Get checkout",
|
|
@@ -30795,6 +31010,7 @@ var checkoutTools = [
|
|
|
30795
31010
|
},
|
|
30796
31011
|
{
|
|
30797
31012
|
name: "ls_list_checkouts",
|
|
31013
|
+
authorityClass: "read",
|
|
30798
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.",
|
|
30799
31015
|
annotations: {
|
|
30800
31016
|
title: "List checkouts",
|
|
@@ -30814,6 +31030,7 @@ var checkoutTools = [
|
|
|
30814
31030
|
},
|
|
30815
31031
|
{
|
|
30816
31032
|
name: "ls_create_checkout",
|
|
31033
|
+
authorityClass: "mutate",
|
|
30817
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.",
|
|
30818
31035
|
annotations: {
|
|
30819
31036
|
title: "Create checkout",
|
|
@@ -30874,6 +31091,7 @@ var checkoutTools = [
|
|
|
30874
31091
|
var customerTools = [
|
|
30875
31092
|
{
|
|
30876
31093
|
name: "ls_get_customer",
|
|
31094
|
+
authorityClass: "pii",
|
|
30877
31095
|
description: "Get a specific customer by ID, including name, email, city, country, MRR, total revenue, and customer portal URL.",
|
|
30878
31096
|
annotations: {
|
|
30879
31097
|
title: "Get customer",
|
|
@@ -30890,6 +31108,7 @@ var customerTools = [
|
|
|
30890
31108
|
},
|
|
30891
31109
|
{
|
|
30892
31110
|
name: "ls_list_customers",
|
|
31111
|
+
authorityClass: "pii",
|
|
30893
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.",
|
|
30894
31113
|
annotations: {
|
|
30895
31114
|
title: "List customers",
|
|
@@ -30909,6 +31128,7 @@ var customerTools = [
|
|
|
30909
31128
|
},
|
|
30910
31129
|
{
|
|
30911
31130
|
name: "ls_create_customer",
|
|
31131
|
+
authorityClass: "pii",
|
|
30912
31132
|
description: "Create a new customer in a store.",
|
|
30913
31133
|
annotations: {
|
|
30914
31134
|
title: "Create customer",
|
|
@@ -30946,6 +31166,7 @@ var customerTools = [
|
|
|
30946
31166
|
},
|
|
30947
31167
|
{
|
|
30948
31168
|
name: "ls_update_customer",
|
|
31169
|
+
authorityClass: "pii",
|
|
30949
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.",
|
|
30950
31171
|
annotations: {
|
|
30951
31172
|
title: "Update customer",
|
|
@@ -30991,6 +31212,7 @@ var customerTools = [
|
|
|
30991
31212
|
},
|
|
30992
31213
|
{
|
|
30993
31214
|
name: "ls_archive_customer",
|
|
31215
|
+
authorityClass: "pii",
|
|
30994
31216
|
description: "Archive a customer. Sets their status to 'archived'. This is reversible by updating their status back.",
|
|
30995
31217
|
annotations: {
|
|
30996
31218
|
title: "Archive customer",
|
|
@@ -31018,6 +31240,7 @@ var customerTools = [
|
|
|
31018
31240
|
var discountRedemptionTools = [
|
|
31019
31241
|
{
|
|
31020
31242
|
name: "ls_get_discount_redemption",
|
|
31243
|
+
authorityClass: "read",
|
|
31021
31244
|
description: "Get a specific discount redemption by ID, showing when and where a discount was used.",
|
|
31022
31245
|
annotations: {
|
|
31023
31246
|
title: "Get discount redemption",
|
|
@@ -31034,6 +31257,7 @@ var discountRedemptionTools = [
|
|
|
31034
31257
|
},
|
|
31035
31258
|
{
|
|
31036
31259
|
name: "ls_list_discount_redemptions",
|
|
31260
|
+
authorityClass: "read",
|
|
31037
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.",
|
|
31038
31262
|
annotations: {
|
|
31039
31263
|
title: "List discount redemptions",
|
|
@@ -31058,6 +31282,7 @@ var discountRedemptionTools = [
|
|
|
31058
31282
|
var discountTools = [
|
|
31059
31283
|
{
|
|
31060
31284
|
name: "ls_get_discount",
|
|
31285
|
+
authorityClass: "read",
|
|
31061
31286
|
description: "Get a specific discount by ID, including code, amount, type, and usage limits.",
|
|
31062
31287
|
annotations: {
|
|
31063
31288
|
title: "Get discount",
|
|
@@ -31074,6 +31299,7 @@ var discountTools = [
|
|
|
31074
31299
|
},
|
|
31075
31300
|
{
|
|
31076
31301
|
name: "ls_list_discounts",
|
|
31302
|
+
authorityClass: "read",
|
|
31077
31303
|
description: "List all discounts, optionally filtered by store. Results are paginated \u2014 check meta.page in the response for currentPage, lastPage, and total.",
|
|
31078
31304
|
annotations: {
|
|
31079
31305
|
title: "List discounts",
|
|
@@ -31092,6 +31318,7 @@ var discountTools = [
|
|
|
31092
31318
|
},
|
|
31093
31319
|
{
|
|
31094
31320
|
name: "ls_create_discount",
|
|
31321
|
+
authorityClass: "mutate",
|
|
31095
31322
|
description: "Create a new discount code. Supports percentage or fixed amount discounts with optional duration and usage limits.",
|
|
31096
31323
|
annotations: {
|
|
31097
31324
|
title: "Create discount",
|
|
@@ -31150,6 +31377,7 @@ var discountTools = [
|
|
|
31150
31377
|
},
|
|
31151
31378
|
{
|
|
31152
31379
|
name: "ls_delete_discount",
|
|
31380
|
+
authorityClass: "mutate",
|
|
31153
31381
|
description: "Permanently delete a discount. This is irreversible.",
|
|
31154
31382
|
annotations: {
|
|
31155
31383
|
title: "Delete discount",
|
|
@@ -31171,6 +31399,7 @@ var discountTools = [
|
|
|
31171
31399
|
var fileTools = [
|
|
31172
31400
|
{
|
|
31173
31401
|
name: "ls_get_file",
|
|
31402
|
+
authorityClass: "read",
|
|
31174
31403
|
description: "Get a specific file by ID, including name, size, download URL, and associated variant.",
|
|
31175
31404
|
annotations: {
|
|
31176
31405
|
title: "Get file",
|
|
@@ -31187,6 +31416,7 @@ var fileTools = [
|
|
|
31187
31416
|
},
|
|
31188
31417
|
{
|
|
31189
31418
|
name: "ls_list_files",
|
|
31419
|
+
authorityClass: "read",
|
|
31190
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.",
|
|
31191
31421
|
annotations: {
|
|
31192
31422
|
title: "List files",
|
|
@@ -31210,6 +31440,7 @@ var fileTools = [
|
|
|
31210
31440
|
var licenseKeyInstanceTools = [
|
|
31211
31441
|
{
|
|
31212
31442
|
name: "ls_get_license_key_instance",
|
|
31443
|
+
authorityClass: "read",
|
|
31213
31444
|
description: "Get a specific license key instance (activation) by ID, including instance name and creation date.",
|
|
31214
31445
|
annotations: {
|
|
31215
31446
|
title: "Get license key instance",
|
|
@@ -31226,6 +31457,7 @@ var licenseKeyInstanceTools = [
|
|
|
31226
31457
|
},
|
|
31227
31458
|
{
|
|
31228
31459
|
name: "ls_list_license_key_instances",
|
|
31460
|
+
authorityClass: "read",
|
|
31229
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.",
|
|
31230
31462
|
annotations: {
|
|
31231
31463
|
title: "List license key instances",
|
|
@@ -31249,6 +31481,7 @@ var licenseKeyInstanceTools = [
|
|
|
31249
31481
|
var licenseKeyTools = [
|
|
31250
31482
|
{
|
|
31251
31483
|
name: "ls_get_license_key",
|
|
31484
|
+
authorityClass: "read",
|
|
31252
31485
|
description: "Get a specific license key by ID, including key value, status, activation limit, and expiry date.",
|
|
31253
31486
|
annotations: {
|
|
31254
31487
|
title: "Get license key",
|
|
@@ -31267,6 +31500,7 @@ var licenseKeyTools = [
|
|
|
31267
31500
|
},
|
|
31268
31501
|
{
|
|
31269
31502
|
name: "ls_list_license_keys",
|
|
31503
|
+
authorityClass: "read",
|
|
31270
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.",
|
|
31271
31505
|
annotations: {
|
|
31272
31506
|
title: "List license keys",
|
|
@@ -31295,6 +31529,7 @@ var licenseKeyTools = [
|
|
|
31295
31529
|
},
|
|
31296
31530
|
{
|
|
31297
31531
|
name: "ls_update_license_key",
|
|
31532
|
+
authorityClass: "key",
|
|
31298
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).",
|
|
31299
31534
|
annotations: {
|
|
31300
31535
|
title: "Update license key",
|
|
@@ -31337,6 +31572,7 @@ var licenseKeyTools = [
|
|
|
31337
31572
|
var licenseTools = [
|
|
31338
31573
|
{
|
|
31339
31574
|
name: "ls_activate_license",
|
|
31575
|
+
authorityClass: "key",
|
|
31340
31576
|
description: "Activate a license key for an instance. Does not require an API key \u2014 uses the license key itself for auth.",
|
|
31341
31577
|
annotations: {
|
|
31342
31578
|
title: "Activate license",
|
|
@@ -31358,6 +31594,7 @@ var licenseTools = [
|
|
|
31358
31594
|
},
|
|
31359
31595
|
{
|
|
31360
31596
|
name: "ls_validate_license",
|
|
31597
|
+
authorityClass: "read",
|
|
31361
31598
|
description: "Validate a license key or specific instance. Does not require an API key \u2014 uses the license key itself for auth.",
|
|
31362
31599
|
annotations: {
|
|
31363
31600
|
title: "Validate license",
|
|
@@ -31378,6 +31615,7 @@ var licenseTools = [
|
|
|
31378
31615
|
},
|
|
31379
31616
|
{
|
|
31380
31617
|
name: "ls_deactivate_license",
|
|
31618
|
+
authorityClass: "key",
|
|
31381
31619
|
description: "Deactivate a license key instance. Does not require an API key \u2014 uses the license key itself for auth.",
|
|
31382
31620
|
annotations: {
|
|
31383
31621
|
title: "Deactivate license",
|
|
@@ -31403,6 +31641,7 @@ var licenseTools = [
|
|
|
31403
31641
|
var orderItemTools = [
|
|
31404
31642
|
{
|
|
31405
31643
|
name: "ls_get_order_item",
|
|
31644
|
+
authorityClass: "read",
|
|
31406
31645
|
description: "Get a specific order item by ID, including product name, variant, price, and quantity.",
|
|
31407
31646
|
annotations: {
|
|
31408
31647
|
title: "Get order item",
|
|
@@ -31419,6 +31658,7 @@ var orderItemTools = [
|
|
|
31419
31658
|
},
|
|
31420
31659
|
{
|
|
31421
31660
|
name: "ls_list_order_items",
|
|
31661
|
+
authorityClass: "read",
|
|
31422
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.",
|
|
31423
31663
|
annotations: {
|
|
31424
31664
|
title: "List order items",
|
|
@@ -31444,6 +31684,7 @@ var orderItemTools = [
|
|
|
31444
31684
|
var orderTools = [
|
|
31445
31685
|
{
|
|
31446
31686
|
name: "ls_get_order",
|
|
31687
|
+
authorityClass: "read",
|
|
31447
31688
|
description: "Get a specific order by ID, including status, total, currency, customer info, and payment details.",
|
|
31448
31689
|
annotations: {
|
|
31449
31690
|
title: "Get order",
|
|
@@ -31462,6 +31703,7 @@ var orderTools = [
|
|
|
31462
31703
|
},
|
|
31463
31704
|
{
|
|
31464
31705
|
name: "ls_list_orders",
|
|
31706
|
+
authorityClass: "read",
|
|
31465
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.",
|
|
31466
31708
|
annotations: {
|
|
31467
31709
|
title: "List orders",
|
|
@@ -31483,6 +31725,7 @@ var orderTools = [
|
|
|
31483
31725
|
},
|
|
31484
31726
|
{
|
|
31485
31727
|
name: "ls_generate_order_invoice",
|
|
31728
|
+
authorityClass: "mutate",
|
|
31486
31729
|
description: "Generate a PDF invoice for an order. Returns a download URL for the invoice.",
|
|
31487
31730
|
annotations: {
|
|
31488
31731
|
title: "Generate order invoice",
|
|
@@ -31518,6 +31761,7 @@ var orderTools = [
|
|
|
31518
31761
|
},
|
|
31519
31762
|
{
|
|
31520
31763
|
name: "ls_refund_order",
|
|
31764
|
+
authorityClass: "money",
|
|
31521
31765
|
description: "Issue a refund for an order. This is irreversible \u2014 the refund amount is in cents (e.g. 1000 = $10.00).",
|
|
31522
31766
|
annotations: {
|
|
31523
31767
|
title: "Refund order",
|
|
@@ -31543,6 +31787,7 @@ var orderTools = [
|
|
|
31543
31787
|
var priceTools = [
|
|
31544
31788
|
{
|
|
31545
31789
|
name: "ls_get_price",
|
|
31790
|
+
authorityClass: "read",
|
|
31546
31791
|
description: "Get a specific price by ID, including amount, currency, and billing interval.",
|
|
31547
31792
|
annotations: {
|
|
31548
31793
|
title: "Get price",
|
|
@@ -31559,6 +31804,7 @@ var priceTools = [
|
|
|
31559
31804
|
},
|
|
31560
31805
|
{
|
|
31561
31806
|
name: "ls_list_prices",
|
|
31807
|
+
authorityClass: "read",
|
|
31562
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.",
|
|
31563
31809
|
annotations: {
|
|
31564
31810
|
title: "List prices",
|
|
@@ -31582,6 +31828,7 @@ var priceTools = [
|
|
|
31582
31828
|
var productTools = [
|
|
31583
31829
|
{
|
|
31584
31830
|
name: "ls_get_product",
|
|
31831
|
+
authorityClass: "read",
|
|
31585
31832
|
description: "Get a specific product by ID, including name, description, price, and status.",
|
|
31586
31833
|
annotations: {
|
|
31587
31834
|
title: "Get product",
|
|
@@ -31598,6 +31845,7 @@ var productTools = [
|
|
|
31598
31845
|
},
|
|
31599
31846
|
{
|
|
31600
31847
|
name: "ls_list_products",
|
|
31848
|
+
authorityClass: "read",
|
|
31601
31849
|
description: "List all products, optionally filtered by store. Results are paginated \u2014 check meta.page in the response for currentPage, lastPage, and total.",
|
|
31602
31850
|
annotations: {
|
|
31603
31851
|
title: "List products",
|
|
@@ -31620,6 +31868,7 @@ var productTools = [
|
|
|
31620
31868
|
var storeTools = [
|
|
31621
31869
|
{
|
|
31622
31870
|
name: "ls_get_store",
|
|
31871
|
+
authorityClass: "read",
|
|
31623
31872
|
description: "Get a specific store by ID, including name, slug, currency, and sales statistics.",
|
|
31624
31873
|
annotations: {
|
|
31625
31874
|
title: "Get store",
|
|
@@ -31638,6 +31887,7 @@ var storeTools = [
|
|
|
31638
31887
|
},
|
|
31639
31888
|
{
|
|
31640
31889
|
name: "ls_list_stores",
|
|
31890
|
+
authorityClass: "read",
|
|
31641
31891
|
description: "List all stores for the authenticated user. Results are paginated \u2014 check meta.page in the response for currentPage, lastPage, and total.",
|
|
31642
31892
|
annotations: {
|
|
31643
31893
|
title: "List stores",
|
|
@@ -31661,6 +31911,7 @@ var storeTools = [
|
|
|
31661
31911
|
var subscriptionInvoiceTools = [
|
|
31662
31912
|
{
|
|
31663
31913
|
name: "ls_get_subscription_invoice",
|
|
31914
|
+
authorityClass: "read",
|
|
31664
31915
|
description: "Get a specific subscription invoice by ID, including status, total, billing reason, and payment details.",
|
|
31665
31916
|
annotations: {
|
|
31666
31917
|
title: "Get subscription invoice",
|
|
@@ -31677,6 +31928,7 @@ var subscriptionInvoiceTools = [
|
|
|
31677
31928
|
},
|
|
31678
31929
|
{
|
|
31679
31930
|
name: "ls_list_subscription_invoices",
|
|
31931
|
+
authorityClass: "read",
|
|
31680
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.",
|
|
31681
31933
|
annotations: {
|
|
31682
31934
|
title: "List subscription invoices",
|
|
@@ -31703,6 +31955,7 @@ var subscriptionInvoiceTools = [
|
|
|
31703
31955
|
},
|
|
31704
31956
|
{
|
|
31705
31957
|
name: "ls_generate_subscription_invoice",
|
|
31958
|
+
authorityClass: "mutate",
|
|
31706
31959
|
description: "Generate a PDF invoice for a subscription invoice. Returns a download URL.",
|
|
31707
31960
|
annotations: {
|
|
31708
31961
|
title: "Generate subscription invoice",
|
|
@@ -31740,6 +31993,7 @@ var subscriptionInvoiceTools = [
|
|
|
31740
31993
|
},
|
|
31741
31994
|
{
|
|
31742
31995
|
name: "ls_refund_subscription_invoice",
|
|
31996
|
+
authorityClass: "money",
|
|
31743
31997
|
description: "Issue a refund for a subscription invoice. This is irreversible \u2014 the refund amount is in cents (e.g. 1000 = $10.00).",
|
|
31744
31998
|
annotations: {
|
|
31745
31999
|
title: "Refund subscription invoice",
|
|
@@ -31769,6 +32023,7 @@ var subscriptionInvoiceTools = [
|
|
|
31769
32023
|
var subscriptionItemTools = [
|
|
31770
32024
|
{
|
|
31771
32025
|
name: "ls_get_subscription_item",
|
|
32026
|
+
authorityClass: "read",
|
|
31772
32027
|
description: "Get a specific subscription item by ID, including quantity, pricing, and associated subscription.",
|
|
31773
32028
|
annotations: {
|
|
31774
32029
|
title: "Get subscription item",
|
|
@@ -31785,6 +32040,7 @@ var subscriptionItemTools = [
|
|
|
31785
32040
|
},
|
|
31786
32041
|
{
|
|
31787
32042
|
name: "ls_list_subscription_items",
|
|
32043
|
+
authorityClass: "read",
|
|
31788
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.",
|
|
31789
32045
|
annotations: {
|
|
31790
32046
|
title: "List subscription items",
|
|
@@ -31805,6 +32061,7 @@ var subscriptionItemTools = [
|
|
|
31805
32061
|
},
|
|
31806
32062
|
{
|
|
31807
32063
|
name: "ls_update_subscription_item",
|
|
32064
|
+
authorityClass: "recurring",
|
|
31808
32065
|
description: "Update a subscription item's quantity. Used for seat-based or quantity-based billing.",
|
|
31809
32066
|
annotations: {
|
|
31810
32067
|
title: "Update subscription item",
|
|
@@ -31829,6 +32086,7 @@ var subscriptionItemTools = [
|
|
|
31829
32086
|
},
|
|
31830
32087
|
{
|
|
31831
32088
|
name: "ls_get_subscription_item_usage",
|
|
32089
|
+
authorityClass: "read",
|
|
31832
32090
|
description: "Get the current usage for a metered subscription item within the current billing period.",
|
|
31833
32091
|
annotations: {
|
|
31834
32092
|
title: "Get subscription item usage",
|
|
@@ -31850,6 +32108,7 @@ var subscriptionItemTools = [
|
|
|
31850
32108
|
var subscriptionTools = [
|
|
31851
32109
|
{
|
|
31852
32110
|
name: "ls_get_subscription",
|
|
32111
|
+
authorityClass: "read",
|
|
31853
32112
|
description: "Get a specific subscription by ID, including status, billing interval, renewal date, and customer info.",
|
|
31854
32113
|
annotations: {
|
|
31855
32114
|
title: "Get subscription",
|
|
@@ -31868,6 +32127,7 @@ var subscriptionTools = [
|
|
|
31868
32127
|
},
|
|
31869
32128
|
{
|
|
31870
32129
|
name: "ls_list_subscriptions",
|
|
32130
|
+
authorityClass: "read",
|
|
31871
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.",
|
|
31872
32132
|
annotations: {
|
|
31873
32133
|
title: "List subscriptions",
|
|
@@ -31902,6 +32162,7 @@ var subscriptionTools = [
|
|
|
31902
32162
|
},
|
|
31903
32163
|
{
|
|
31904
32164
|
name: "ls_update_subscription",
|
|
32165
|
+
authorityClass: "recurring",
|
|
31905
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.",
|
|
31906
32167
|
annotations: {
|
|
31907
32168
|
title: "Update subscription",
|
|
@@ -31953,6 +32214,7 @@ var subscriptionTools = [
|
|
|
31953
32214
|
},
|
|
31954
32215
|
{
|
|
31955
32216
|
name: "ls_cancel_subscription",
|
|
32217
|
+
authorityClass: "recurring",
|
|
31956
32218
|
description: "Cancel a subscription. The subscription remains active until the end of the current billing period, then expires.",
|
|
31957
32219
|
annotations: {
|
|
31958
32220
|
title: "Cancel subscription",
|
|
@@ -31974,6 +32236,7 @@ var subscriptionTools = [
|
|
|
31974
32236
|
var usageRecordTools = [
|
|
31975
32237
|
{
|
|
31976
32238
|
name: "ls_get_usage_record",
|
|
32239
|
+
authorityClass: "read",
|
|
31977
32240
|
description: "Get a specific usage record by ID, including quantity and action type.",
|
|
31978
32241
|
annotations: {
|
|
31979
32242
|
title: "Get usage record",
|
|
@@ -31990,6 +32253,7 @@ var usageRecordTools = [
|
|
|
31990
32253
|
},
|
|
31991
32254
|
{
|
|
31992
32255
|
name: "ls_list_usage_records",
|
|
32256
|
+
authorityClass: "read",
|
|
31993
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.",
|
|
31994
32258
|
annotations: {
|
|
31995
32259
|
title: "List usage records",
|
|
@@ -32009,6 +32273,7 @@ var usageRecordTools = [
|
|
|
32009
32273
|
},
|
|
32010
32274
|
{
|
|
32011
32275
|
name: "ls_create_usage_record",
|
|
32276
|
+
authorityClass: "mutate",
|
|
32012
32277
|
description: "Report usage for a metered subscription item. Use 'increment' action to add to the current usage, or 'set' to replace it.",
|
|
32013
32278
|
annotations: {
|
|
32014
32279
|
title: "Create usage record",
|
|
@@ -32049,6 +32314,7 @@ var usageRecordTools = [
|
|
|
32049
32314
|
var userTools = [
|
|
32050
32315
|
{
|
|
32051
32316
|
name: "ls_get_user",
|
|
32317
|
+
authorityClass: "read",
|
|
32052
32318
|
description: "Get the authenticated user's information including name, email, and avatar.",
|
|
32053
32319
|
annotations: {
|
|
32054
32320
|
title: "Get authenticated user",
|
|
@@ -32068,6 +32334,7 @@ var userTools = [
|
|
|
32068
32334
|
var variantTools = [
|
|
32069
32335
|
{
|
|
32070
32336
|
name: "ls_get_variant",
|
|
32337
|
+
authorityClass: "read",
|
|
32071
32338
|
description: "Get a specific product variant by ID, including price, billing interval, and trial settings.",
|
|
32072
32339
|
annotations: {
|
|
32073
32340
|
title: "Get variant",
|
|
@@ -32084,6 +32351,7 @@ var variantTools = [
|
|
|
32084
32351
|
},
|
|
32085
32352
|
{
|
|
32086
32353
|
name: "ls_list_variants",
|
|
32354
|
+
authorityClass: "read",
|
|
32087
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.",
|
|
32088
32356
|
annotations: {
|
|
32089
32357
|
title: "List variants",
|
|
@@ -32107,6 +32375,7 @@ var variantTools = [
|
|
|
32107
32375
|
var webhookTools = [
|
|
32108
32376
|
{
|
|
32109
32377
|
name: "ls_get_webhook",
|
|
32378
|
+
authorityClass: "read",
|
|
32110
32379
|
description: "Get a specific webhook by ID, including URL, events, and last sent timestamp.",
|
|
32111
32380
|
annotations: {
|
|
32112
32381
|
title: "Get webhook",
|
|
@@ -32123,6 +32392,7 @@ var webhookTools = [
|
|
|
32123
32392
|
},
|
|
32124
32393
|
{
|
|
32125
32394
|
name: "ls_list_webhooks",
|
|
32395
|
+
authorityClass: "read",
|
|
32126
32396
|
description: "List all webhooks, optionally filtered by store. Results are paginated \u2014 check meta.page in the response for currentPage, lastPage, and total.",
|
|
32127
32397
|
annotations: {
|
|
32128
32398
|
title: "List webhooks",
|
|
@@ -32141,6 +32411,7 @@ var webhookTools = [
|
|
|
32141
32411
|
},
|
|
32142
32412
|
{
|
|
32143
32413
|
name: "ls_create_webhook",
|
|
32414
|
+
authorityClass: "webhook",
|
|
32144
32415
|
description: "Create a new webhook to receive event notifications. The signing secret is returned only once \u2014 save it immediately.",
|
|
32145
32416
|
annotations: {
|
|
32146
32417
|
title: "Create webhook",
|
|
@@ -32175,6 +32446,7 @@ var webhookTools = [
|
|
|
32175
32446
|
},
|
|
32176
32447
|
{
|
|
32177
32448
|
name: "ls_update_webhook",
|
|
32449
|
+
authorityClass: "webhook",
|
|
32178
32450
|
description: "Update an existing webhook's URL, events, or secret.",
|
|
32179
32451
|
annotations: {
|
|
32180
32452
|
title: "Update webhook",
|
|
@@ -32205,6 +32477,7 @@ var webhookTools = [
|
|
|
32205
32477
|
},
|
|
32206
32478
|
{
|
|
32207
32479
|
name: "ls_delete_webhook",
|
|
32480
|
+
authorityClass: "webhook",
|
|
32208
32481
|
description: "Permanently delete a webhook. This is irreversible.",
|
|
32209
32482
|
annotations: {
|
|
32210
32483
|
title: "Delete webhook",
|
|
@@ -32223,7 +32496,7 @@ var webhookTools = [
|
|
|
32223
32496
|
];
|
|
32224
32497
|
|
|
32225
32498
|
// src/index.ts
|
|
32226
|
-
var version2 = true ? "0.
|
|
32499
|
+
var version2 = true ? "0.9.0" : (await null).createRequire(import.meta.url)("../package.json").version;
|
|
32227
32500
|
var subcommand = process.argv[2];
|
|
32228
32501
|
if (subcommand === "version" || subcommand === "--version") {
|
|
32229
32502
|
console.log(version2);
|
|
@@ -32266,12 +32539,14 @@ for (const tool of allTools) {
|
|
|
32266
32539
|
const isDestructive = isDestructiveCall(tool, input);
|
|
32267
32540
|
const start = Date.now();
|
|
32268
32541
|
try {
|
|
32542
|
+
checkClassAllowed(tool.authorityClass);
|
|
32543
|
+
checkClassRateLimit(tool.authorityClass);
|
|
32269
32544
|
if (isDestructive) checkDestructiveRateLimit();
|
|
32270
32545
|
checkStoreScopedToolInput(tool, input);
|
|
32271
32546
|
const result = await tool.handler(input);
|
|
32272
32547
|
const response = result;
|
|
32273
32548
|
const latency_ms = Date.now() - start;
|
|
32274
|
-
|
|
32549
|
+
const successEntry = {
|
|
32275
32550
|
event: "tool_call",
|
|
32276
32551
|
tool: tool.name,
|
|
32277
32552
|
status: response.ok ? "ok" : "error",
|
|
@@ -32280,7 +32555,11 @@ for (const tool of allTools) {
|
|
|
32280
32555
|
error: response.ok ? void 0 : response.error,
|
|
32281
32556
|
audit: isDestructive ? true : void 0,
|
|
32282
32557
|
inputs: isDestructive ? redactSecrets(input) : void 0
|
|
32283
|
-
}
|
|
32558
|
+
};
|
|
32559
|
+
logEvent(successEntry);
|
|
32560
|
+
if (isDestructive) {
|
|
32561
|
+
pushAuditEntry({ ts: (/* @__PURE__ */ new Date()).toISOString(), ...successEntry });
|
|
32562
|
+
}
|
|
32284
32563
|
if (!response.ok) {
|
|
32285
32564
|
return {
|
|
32286
32565
|
content: [
|
|
@@ -32299,7 +32578,7 @@ for (const tool of allTools) {
|
|
|
32299
32578
|
} catch (err) {
|
|
32300
32579
|
const message = err instanceof Error ? err.message : String(err);
|
|
32301
32580
|
const latency_ms = Date.now() - start;
|
|
32302
|
-
|
|
32581
|
+
const errorEntry = {
|
|
32303
32582
|
event: "tool_call",
|
|
32304
32583
|
tool: tool.name,
|
|
32305
32584
|
status: err instanceof GuardrailError ? "guardrail_block" : "exception",
|
|
@@ -32307,7 +32586,11 @@ for (const tool of allTools) {
|
|
|
32307
32586
|
error: message,
|
|
32308
32587
|
audit: isDestructive ? true : void 0,
|
|
32309
32588
|
inputs: isDestructive ? redactSecrets(input) : void 0
|
|
32310
|
-
}
|
|
32589
|
+
};
|
|
32590
|
+
logEvent(errorEntry);
|
|
32591
|
+
if (isDestructive) {
|
|
32592
|
+
pushAuditEntry({ ts: (/* @__PURE__ */ new Date()).toISOString(), ...errorEntry });
|
|
32593
|
+
}
|
|
32311
32594
|
return {
|
|
32312
32595
|
content: [{ type: "text", text: `Error: ${message}` }],
|
|
32313
32596
|
isError: true
|
|
@@ -32316,6 +32599,27 @@ for (const tool of allTools) {
|
|
|
32316
32599
|
}
|
|
32317
32600
|
);
|
|
32318
32601
|
}
|
|
32602
|
+
server.resource(
|
|
32603
|
+
"Recent destructive-call audit log",
|
|
32604
|
+
"lemonsqueezy://audit-log",
|
|
32605
|
+
{
|
|
32606
|
+
description: "The most recent destructive tool calls and their outcomes (rate limit, refund cap, etc.). Bounded ring buffer; resets on server restart.",
|
|
32607
|
+
mimeType: "application/x-ndjson"
|
|
32608
|
+
},
|
|
32609
|
+
async (uri) => {
|
|
32610
|
+
const entries = readAuditEntries();
|
|
32611
|
+
const text = entries.map((e) => JSON.stringify(e)).join("\n");
|
|
32612
|
+
return {
|
|
32613
|
+
contents: [
|
|
32614
|
+
{
|
|
32615
|
+
uri: uri.href,
|
|
32616
|
+
mimeType: "application/x-ndjson",
|
|
32617
|
+
text
|
|
32618
|
+
}
|
|
32619
|
+
]
|
|
32620
|
+
};
|
|
32621
|
+
}
|
|
32622
|
+
);
|
|
32319
32623
|
var transport = new StdioServerTransport();
|
|
32320
32624
|
await server.connect(transport);
|
|
32321
32625
|
//# sourceMappingURL=index.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yawlabs/lemonsqueezy-mcp",
|
|
3
|
-
"version": "0.
|
|
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>",
|