@yawlabs/lemonsqueezy-mcp 0.9.0 → 0.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +32 -13
- package/dist/index.js +389 -347
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -62,7 +62,7 @@ docker build -t yawlabs/lemonsqueezy-mcp .
|
|
|
62
62
|
docker run --rm -i -e LEMONSQUEEZY_API_KEY="your-api-key" yawlabs/lemonsqueezy-mcp
|
|
63
63
|
```
|
|
64
64
|
|
|
65
|
-
A
|
|
65
|
+
A matching `Containerfile` is provided for Podman users. It is generated from `Dockerfile` via `npm run gen:containerfile`; CI calls `npm run check:containerfile` so a divergent edit fails review rather than drifting silently.
|
|
66
66
|
|
|
67
67
|
### Claude Code
|
|
68
68
|
|
|
@@ -208,9 +208,10 @@ Add to `claude_desktop_config.json`:
|
|
|
208
208
|
- **Zero runtime dependencies** — Single bundled file for instant `npx` startup
|
|
209
209
|
- **License API** — Activate, validate, and deactivate license keys without an API key
|
|
210
210
|
- **MCP annotations** — Every tool declares read-only, destructive, and idempotent hints
|
|
211
|
-
- **Retry with backoff** — 429 and 5xx retries (idempotent methods only) with exponential backoff and
|
|
212
|
-
- **Guardrails** —
|
|
213
|
-
- **
|
|
211
|
+
- **Retry with backoff** — 429 and 5xx retries (idempotent methods only) with exponential backoff, jitter, and a 90s overall-deadline ceiling
|
|
212
|
+
- **Guardrails** — opt-in store allowlist, refund cap, destructive-call rate limit, authority-class disable (`LEMONSQUEEZY_DISABLE_CLASSES`), and per-authority-class rate limits (`LEMONSQUEEZY_RATE_LIMIT_PER_CLASS`)
|
|
213
|
+
- **Audit log MCP Resource** — `lemonsqueezy://audit-log` exposes the last 1000 destructive-call entries as `application/x-ndjson` for clients without stderr access
|
|
214
|
+
- **Structured logging** — opt-in JSON logs to stderr with selectable levels (`error`, `audit`, `all`)
|
|
214
215
|
|
|
215
216
|
## Configuration
|
|
216
217
|
|
|
@@ -221,9 +222,9 @@ All configuration is via environment variables. Only `LEMONSQUEEZY_API_KEY` (or
|
|
|
221
222
|
| `LEMONSQUEEZY_API_KEY` | LemonSqueezy API token. |
|
|
222
223
|
| `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
224
|
| `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. |
|
|
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.
|
|
225
|
+
| `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. LemonSqueezy API keys are issued at the account level and authorize access to every store in that account, so this allowlist is the only in-process store boundary the server can enforce. Pair it with `LEMONSQUEEZY_MAX_REFUND_AMOUNT_CENTS` / `LEMONSQUEEZY_DESTRUCTIVE_RATE_LIMIT` / `LEMONSQUEEZY_RATE_LIMIT_PER_CLASS` for defense in depth, and — if your account hosts multiple stores you don't want exposed to the same agent — keep those stores under a separate LemonSqueezy account whose API key isn't reachable from this server. |
|
|
225
226
|
| `LEMONSQUEEZY_MAX_REFUND_AMOUNT_CENTS` | Rejects `ls_refund_order` and `ls_refund_subscription_invoice` calls above this amount. |
|
|
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
|
|
227
|
+
| `LEMONSQUEEZY_DESTRUCTIVE_RATE_LIMIT` | Positive integer. 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 every refund, cancellation, archive, and delete tool, plus the input-dependent destructive paths: `ls_update_license_key` calls that set `disabled: true` *or* change `activationLimit`, `ls_update_subscription` calls that pause or switch plan, and `ls_update_customer` calls with `status: "archived"`. |
|
|
227
228
|
| `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
229
|
| `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. |
|
|
229
230
|
| `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. |
|
|
@@ -238,7 +239,11 @@ HTTP errors include the upstream `X-Request-Id` when present, so support tickets
|
|
|
238
239
|
|
|
239
240
|
## Authority classes
|
|
240
241
|
|
|
241
|
-
|
|
242
|
+
**The strongest access control LemonSqueezy itself exposes is the API key boundary.** A LemonSqueezy API key authorizes its full account — every store, every tool — and the only way to deny a class of authority through LemonSqueezy is to not give the API key to the agent in the first place. LemonSqueezy's team-membership UI scopes which humans can do which actions in the dashboard, but the public API key inherits the full authority of the account it was issued under; there's no "this key can read but not refund" toggle. So the authoritative boundary, as far as the upstream API is concerned, is *which API key the agent has*.
|
|
243
|
+
|
|
244
|
+
That means the env vars below are the primary in-process control surface for anything LemonSqueezy can't gate by itself — per-class rate ceilings (`RATE_LIMIT_PER_CLASS`), deploy-time class disables (`DISABLE_CLASSES`), refund caps (`MAX_REFUND_AMOUNT_CENTS`), and the audit log. They are belt-and-braces in the sense that an operator who can change the server's env can remove them; they are load-bearing in the sense that LemonSqueezy has no equivalent. If you need an agent that genuinely cannot reach a store or a class of action, the durable answer is a separate LemonSqueezy account whose key is never handed to that agent.
|
|
245
|
+
|
|
246
|
+
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.
|
|
242
247
|
|
|
243
248
|
| Class | What it covers | Example tools |
|
|
244
249
|
| --- | --- | --- |
|
|
@@ -250,9 +255,14 @@ Every tool is tagged with an **authority class** — a label for the kind of bus
|
|
|
250
255
|
| `key` | License-key admin (activate, deactivate, disable, change activation limit). | `ls_update_license_key`, `ls_activate_license`, `ls_deactivate_license` |
|
|
251
256
|
| `webhook` | Webhook configuration — affects the trust surface other systems rely on. | `ls_create_webhook`, `ls_update_webhook`, `ls_delete_webhook` |
|
|
252
257
|
|
|
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,
|
|
258
|
+
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, set `LEMONSQUEEZY_DISABLE_CLASSES=pii` and — if the agent must never under any circumstances touch that data — also issue its API key from a separate LemonSqueezy account that doesn't host customer records you care about.
|
|
259
|
+
|
|
260
|
+
The two opt-in env vars that consume this taxonomy:
|
|
254
261
|
|
|
255
|
-
|
|
262
|
+
- `LEMONSQUEEZY_RATE_LIMIT_PER_CLASS` — caps the call rate per class. LemonSqueezy permissions cannot express "max 2 refunds per hour"; this is the only place that policy can live. **This is the load-bearing one for runaway-agent prevention.**
|
|
263
|
+
- `LEMONSQUEEZY_DISABLE_CLASSES` — blocks a class outright. Useful when fast deploy-time toggles matter — flipping `DISABLE_CLASSES=pii,mutate,money,recurring,key,webhook` to lock an analytics deployment into pure reads is a one-line config change. An operator who can change the server's env can also remove this gate, so for an authoritative deny use a separate LemonSqueezy account whose API key the agent never sees.
|
|
264
|
+
|
|
265
|
+
Both are opt-in; with neither set, behavior is unchanged from prior versions.
|
|
256
266
|
|
|
257
267
|
## Resources
|
|
258
268
|
|
|
@@ -260,16 +270,16 @@ The server exposes one MCP Resource for clients that prefer structural retrieval
|
|
|
260
270
|
|
|
261
271
|
| URI | MIME type | Contents |
|
|
262
272
|
| --- | --- | --- |
|
|
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.
|
|
273
|
+
| `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. Redaction runs on the input payload before it reaches the buffer: any object key whose name matches a credential / PII pattern (`secret`, `password`, `token`, `api_key`, `bearer`, `authorization`, `signing_secret`, `private_key`, `pin`, `ssn`, `social_security_number`, `credit_card`, `card_number`, `cvv`, `cvc` — case-insensitive, whole-word) AND any string value matching the JWT bearer-token shape (`eyJ…`-prefixed, three base64url segments) is replaced with `[REDACTED]`. Ordinary identifiers (`licenseKey`, `instanceId`, `storeId`, `orderId`, `webhookId`) and UUID-shaped values are preserved. |
|
|
264
274
|
|
|
265
275
|
## Operating the server unattended
|
|
266
276
|
|
|
267
277
|
For unattended/agentic use against a live store, we recommend:
|
|
268
278
|
|
|
269
|
-
1.
|
|
279
|
+
1. Issue an API key under a LemonSqueezy account that hosts only the store(s) the agent is allowed to touch — LemonSqueezy doesn't expose per-store API-key scoping, so account separation is the durable store boundary. Set `LEMONSQUEEZY_ALLOWED_STORE_IDS` to the same set as a belt-and-braces in-process gate on the tools that take a `storeId`.
|
|
270
280
|
2. Set `LEMONSQUEEZY_MAX_REFUND_AMOUNT_CENTS` to a per-call cap well below any single-refund expectation.
|
|
271
281
|
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
|
|
282
|
+
4. If a class shouldn't be reachable at all (e.g. an analytics agent that needs only `read`), set `LEMONSQUEEZY_DISABLE_CLASSES` to the classes you want refused. The server rejects them before the API call is built. For an irrevocable deny, also issue the agent's API key from a separate LemonSqueezy account.
|
|
273
283
|
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
284
|
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.
|
|
275
285
|
|
|
@@ -290,6 +300,13 @@ npm test # full unit + handler suite
|
|
|
290
300
|
npm run test:integration # requires LEMONSQUEEZY_TEST_API_KEY + LEMONSQUEEZY_TEST_STORE_ID
|
|
291
301
|
```
|
|
292
302
|
|
|
303
|
+
`Containerfile` is generated from `Dockerfile`. After editing `Dockerfile`:
|
|
304
|
+
|
|
305
|
+
```bash
|
|
306
|
+
npm run gen:containerfile # regenerate Containerfile
|
|
307
|
+
npm run check:containerfile # CI runs this; non-zero exit means the two have drifted
|
|
308
|
+
```
|
|
309
|
+
|
|
293
310
|
## Releasing
|
|
294
311
|
|
|
295
312
|
Two paths from a clean checkout of `main`. Both produce the same artifact (npm publish with provenance + GitHub release).
|
|
@@ -313,7 +330,7 @@ git push origin main --follow-tags
|
|
|
313
330
|
gh run list --limit 2
|
|
314
331
|
```
|
|
315
332
|
|
|
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.
|
|
333
|
+
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, then a publish to the [Official MCP Registry](https://registry.modelcontextprotocol.io) via GitHub OIDC (no `MCP_*` secret needed; the namespace `io.github.YawLabs/*` is authorized purely from the OIDC `repository_owner` claim). No local `npm login` needed.
|
|
317
334
|
|
|
318
335
|
### 2. Local end-to-end
|
|
319
336
|
|
|
@@ -328,6 +345,8 @@ npm login --auth-type=web # publisher of @yawlabs/lemonsqueezy-mcp
|
|
|
328
345
|
gh auth login # GitHub CLI for the release-creation step
|
|
329
346
|
```
|
|
330
347
|
|
|
348
|
+
The local path does **not** publish to the Official MCP Registry — that step lives only in CI and depends on a GitHub Actions OIDC token. To push a locally-released version to the registry, install [`mcp-publisher`](https://github.com/modelcontextprotocol/registry/releases), run `mcp-publisher login github` (interactive OAuth), then `mcp-publisher publish` from the repo root.
|
|
349
|
+
|
|
331
350
|
## License
|
|
332
351
|
|
|
333
352
|
MIT
|
package/dist/index.js
CHANGED
|
@@ -3133,20 +3133,20 @@ var require_utils = __commonJS({
|
|
|
3133
3133
|
return acc;
|
|
3134
3134
|
}
|
|
3135
3135
|
var nonSimpleDomain = RegExp.prototype.test.bind(/[^!"$&'()*+,\-.;=_`a-z{}~]/u);
|
|
3136
|
-
function consumeIsZone(
|
|
3137
|
-
|
|
3136
|
+
function consumeIsZone(buffer) {
|
|
3137
|
+
buffer.length = 0;
|
|
3138
3138
|
return true;
|
|
3139
3139
|
}
|
|
3140
|
-
function consumeHextets(
|
|
3141
|
-
if (
|
|
3142
|
-
const hex3 = stringArrayToHexStripped(
|
|
3140
|
+
function consumeHextets(buffer, address, output) {
|
|
3141
|
+
if (buffer.length) {
|
|
3142
|
+
const hex3 = stringArrayToHexStripped(buffer);
|
|
3143
3143
|
if (hex3 !== "") {
|
|
3144
3144
|
address.push(hex3);
|
|
3145
3145
|
} else {
|
|
3146
3146
|
output.error = true;
|
|
3147
3147
|
return false;
|
|
3148
3148
|
}
|
|
3149
|
-
|
|
3149
|
+
buffer.length = 0;
|
|
3150
3150
|
}
|
|
3151
3151
|
return true;
|
|
3152
3152
|
}
|
|
@@ -3154,7 +3154,7 @@ var require_utils = __commonJS({
|
|
|
3154
3154
|
let tokenCount = 0;
|
|
3155
3155
|
const output = { error: false, address: "", zone: "" };
|
|
3156
3156
|
const address = [];
|
|
3157
|
-
const
|
|
3157
|
+
const buffer = [];
|
|
3158
3158
|
let endipv6Encountered = false;
|
|
3159
3159
|
let endIpv6 = false;
|
|
3160
3160
|
let consume = consumeHextets;
|
|
@@ -3167,7 +3167,7 @@ var require_utils = __commonJS({
|
|
|
3167
3167
|
if (endipv6Encountered === true) {
|
|
3168
3168
|
endIpv6 = true;
|
|
3169
3169
|
}
|
|
3170
|
-
if (!consume(
|
|
3170
|
+
if (!consume(buffer, address, output)) {
|
|
3171
3171
|
break;
|
|
3172
3172
|
}
|
|
3173
3173
|
if (++tokenCount > 7) {
|
|
@@ -3180,22 +3180,22 @@ var require_utils = __commonJS({
|
|
|
3180
3180
|
address.push(":");
|
|
3181
3181
|
continue;
|
|
3182
3182
|
} else if (cursor === "%") {
|
|
3183
|
-
if (!consume(
|
|
3183
|
+
if (!consume(buffer, address, output)) {
|
|
3184
3184
|
break;
|
|
3185
3185
|
}
|
|
3186
3186
|
consume = consumeIsZone;
|
|
3187
3187
|
} else {
|
|
3188
|
-
|
|
3188
|
+
buffer.push(cursor);
|
|
3189
3189
|
continue;
|
|
3190
3190
|
}
|
|
3191
3191
|
}
|
|
3192
|
-
if (
|
|
3192
|
+
if (buffer.length) {
|
|
3193
3193
|
if (consume === consumeIsZone) {
|
|
3194
|
-
output.zone =
|
|
3194
|
+
output.zone = buffer.join("");
|
|
3195
3195
|
} else if (endIpv6) {
|
|
3196
|
-
address.push(
|
|
3196
|
+
address.push(buffer.join(""));
|
|
3197
3197
|
} else {
|
|
3198
|
-
address.push(stringArrayToHexStripped(
|
|
3198
|
+
address.push(stringArrayToHexStripped(buffer));
|
|
3199
3199
|
}
|
|
3200
3200
|
}
|
|
3201
3201
|
output.address = address.join("");
|
|
@@ -10015,8 +10015,8 @@ var ZodSet = class _ZodSet extends ZodType {
|
|
|
10015
10015
|
maxSize: { value: maxSize, message: errorUtil.toString(message) }
|
|
10016
10016
|
});
|
|
10017
10017
|
}
|
|
10018
|
-
size(
|
|
10019
|
-
return this.min(
|
|
10018
|
+
size(size2, message) {
|
|
10019
|
+
return this.min(size2, message).max(size2, message);
|
|
10020
10020
|
}
|
|
10021
10021
|
nonempty(message) {
|
|
10022
10022
|
return this.min(1, message);
|
|
@@ -12392,8 +12392,8 @@ var $ZodCheckMaxSize = /* @__PURE__ */ $constructor("$ZodCheckMaxSize", (inst, d
|
|
|
12392
12392
|
});
|
|
12393
12393
|
inst._zod.check = (payload) => {
|
|
12394
12394
|
const input = payload.value;
|
|
12395
|
-
const
|
|
12396
|
-
if (
|
|
12395
|
+
const size2 = input.size;
|
|
12396
|
+
if (size2 <= def.maximum)
|
|
12397
12397
|
return;
|
|
12398
12398
|
payload.issues.push({
|
|
12399
12399
|
origin: getSizableOrigin(input),
|
|
@@ -12420,8 +12420,8 @@ var $ZodCheckMinSize = /* @__PURE__ */ $constructor("$ZodCheckMinSize", (inst, d
|
|
|
12420
12420
|
});
|
|
12421
12421
|
inst._zod.check = (payload) => {
|
|
12422
12422
|
const input = payload.value;
|
|
12423
|
-
const
|
|
12424
|
-
if (
|
|
12423
|
+
const size2 = input.size;
|
|
12424
|
+
if (size2 >= def.minimum)
|
|
12425
12425
|
return;
|
|
12426
12426
|
payload.issues.push({
|
|
12427
12427
|
origin: getSizableOrigin(input),
|
|
@@ -12449,10 +12449,10 @@ var $ZodCheckSizeEquals = /* @__PURE__ */ $constructor("$ZodCheckSizeEquals", (i
|
|
|
12449
12449
|
});
|
|
12450
12450
|
inst._zod.check = (payload) => {
|
|
12451
12451
|
const input = payload.value;
|
|
12452
|
-
const
|
|
12453
|
-
if (
|
|
12452
|
+
const size2 = input.size;
|
|
12453
|
+
if (size2 === def.size)
|
|
12454
12454
|
return;
|
|
12455
|
-
const tooBig =
|
|
12455
|
+
const tooBig = size2 > def.size;
|
|
12456
12456
|
payload.issues.push({
|
|
12457
12457
|
origin: getSizableOrigin(input),
|
|
12458
12458
|
...tooBig ? { code: "too_big", maximum: def.size } : { code: "too_small", minimum: def.size },
|
|
@@ -14484,12 +14484,12 @@ var $ZodPipe = /* @__PURE__ */ $constructor("$ZodPipe", (inst, def) => {
|
|
|
14484
14484
|
return handlePipeResult(left, def.out, ctx);
|
|
14485
14485
|
};
|
|
14486
14486
|
});
|
|
14487
|
-
function handlePipeResult(left,
|
|
14487
|
+
function handlePipeResult(left, next2, ctx) {
|
|
14488
14488
|
if (left.issues.length) {
|
|
14489
14489
|
left.aborted = true;
|
|
14490
14490
|
return left;
|
|
14491
14491
|
}
|
|
14492
|
-
return
|
|
14492
|
+
return next2._zod.run({ value: left.value, issues: left.issues }, ctx);
|
|
14493
14493
|
}
|
|
14494
14494
|
var $ZodCodec = /* @__PURE__ */ $constructor("$ZodCodec", (inst, def) => {
|
|
14495
14495
|
$ZodType.init(inst, def);
|
|
@@ -20922,11 +20922,11 @@ function _minSize(minimum, params) {
|
|
|
20922
20922
|
});
|
|
20923
20923
|
}
|
|
20924
20924
|
// @__NO_SIDE_EFFECTS__
|
|
20925
|
-
function _size(
|
|
20925
|
+
function _size(size2, params) {
|
|
20926
20926
|
return new $ZodCheckSizeEquals({
|
|
20927
20927
|
check: "size_equals",
|
|
20928
20928
|
...normalizeParams(params),
|
|
20929
|
-
size
|
|
20929
|
+
size: size2
|
|
20930
20930
|
});
|
|
20931
20931
|
}
|
|
20932
20932
|
// @__NO_SIDE_EFFECTS__
|
|
@@ -23878,8 +23878,8 @@ var ZodFile = /* @__PURE__ */ $constructor("ZodFile", (inst, def) => {
|
|
|
23878
23878
|
$ZodFile.init(inst, def);
|
|
23879
23879
|
ZodType2.init(inst, def);
|
|
23880
23880
|
inst._zod.processJSONSchema = (ctx, json2, params) => fileProcessor(inst, ctx, json2, params);
|
|
23881
|
-
inst.min = (
|
|
23882
|
-
inst.max = (
|
|
23881
|
+
inst.min = (size2, params) => inst.check(_minSize(size2, params));
|
|
23882
|
+
inst.max = (size2, params) => inst.check(_maxSize(size2, params));
|
|
23883
23883
|
inst.mime = (types, params) => inst.check(_mime(Array.isArray(types) ? types : [types], params));
|
|
23884
23884
|
});
|
|
23885
23885
|
function file(params) {
|
|
@@ -30200,203 +30200,6 @@ var StdioServerTransport = class {
|
|
|
30200
30200
|
}
|
|
30201
30201
|
};
|
|
30202
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
|
-
|
|
30220
|
-
// src/guardrails.ts
|
|
30221
|
-
var GuardrailError = class extends Error {
|
|
30222
|
-
constructor(message) {
|
|
30223
|
-
super(message);
|
|
30224
|
-
this.name = "GuardrailError";
|
|
30225
|
-
}
|
|
30226
|
-
};
|
|
30227
|
-
var AUTHORITY_CLASSES = ["read", "pii", "mutate", "money", "recurring", "key", "webhook"];
|
|
30228
|
-
function isAuthorityClass(s) {
|
|
30229
|
-
return AUTHORITY_CLASSES.includes(s);
|
|
30230
|
-
}
|
|
30231
|
-
var cachedOptions = null;
|
|
30232
|
-
var destructiveTimestamps = [];
|
|
30233
|
-
var classTimestamps = /* @__PURE__ */ new Map();
|
|
30234
|
-
function readNumber(name, raw) {
|
|
30235
|
-
if (raw === void 0 || raw.trim() === "") return null;
|
|
30236
|
-
const n = Number(raw);
|
|
30237
|
-
if (!Number.isFinite(n) || n < 0) {
|
|
30238
|
-
throw new Error(`${name} must be a non-negative number (got ${JSON.stringify(raw)})`);
|
|
30239
|
-
}
|
|
30240
|
-
return n;
|
|
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
|
-
}
|
|
30295
|
-
function loadOptions() {
|
|
30296
|
-
if (cachedOptions) return cachedOptions;
|
|
30297
|
-
const allowed = process.env.LEMONSQUEEZY_ALLOWED_STORE_IDS;
|
|
30298
|
-
const allowedSet = allowed ? new Set(
|
|
30299
|
-
allowed.split(",").map((s) => s.trim()).filter(Boolean)
|
|
30300
|
-
) : null;
|
|
30301
|
-
cachedOptions = {
|
|
30302
|
-
allowedStoreIds: allowedSet && allowedSet.size > 0 ? allowedSet : null,
|
|
30303
|
-
maxRefundAmountCents: readNumber(
|
|
30304
|
-
"LEMONSQUEEZY_MAX_REFUND_AMOUNT_CENTS",
|
|
30305
|
-
process.env.LEMONSQUEEZY_MAX_REFUND_AMOUNT_CENTS
|
|
30306
|
-
),
|
|
30307
|
-
rateLimitPerMinute: readNumber(
|
|
30308
|
-
"LEMONSQUEEZY_DESTRUCTIVE_RATE_LIMIT",
|
|
30309
|
-
process.env.LEMONSQUEEZY_DESTRUCTIVE_RATE_LIMIT
|
|
30310
|
-
),
|
|
30311
|
-
disabledClasses: parseDisabledClasses(process.env.LEMONSQUEEZY_DISABLE_CLASSES),
|
|
30312
|
-
classRateLimits: parseClassRateLimits(process.env.LEMONSQUEEZY_RATE_LIMIT_PER_CLASS)
|
|
30313
|
-
};
|
|
30314
|
-
return cachedOptions;
|
|
30315
|
-
}
|
|
30316
|
-
function checkStoreAllowed(storeId) {
|
|
30317
|
-
if (!storeId) return;
|
|
30318
|
-
const o = loadOptions();
|
|
30319
|
-
if (!o.allowedStoreIds) return;
|
|
30320
|
-
if (!o.allowedStoreIds.has(String(storeId))) {
|
|
30321
|
-
throw new GuardrailError(`Store ID ${storeId} is not in LEMONSQUEEZY_ALLOWED_STORE_IDS allowlist`);
|
|
30322
|
-
}
|
|
30323
|
-
}
|
|
30324
|
-
function checkRefundAmount(cents) {
|
|
30325
|
-
const o = loadOptions();
|
|
30326
|
-
if (o.maxRefundAmountCents === null) return;
|
|
30327
|
-
if (cents > o.maxRefundAmountCents) {
|
|
30328
|
-
throw new GuardrailError(
|
|
30329
|
-
`Refund amount ${cents} cents exceeds LEMONSQUEEZY_MAX_REFUND_AMOUNT_CENTS (${o.maxRefundAmountCents})`
|
|
30330
|
-
);
|
|
30331
|
-
}
|
|
30332
|
-
}
|
|
30333
|
-
function checkDestructiveRateLimit(now = Date.now()) {
|
|
30334
|
-
const o = loadOptions();
|
|
30335
|
-
if (o.rateLimitPerMinute === null) return;
|
|
30336
|
-
const cutoff = now - 6e4;
|
|
30337
|
-
destructiveTimestamps = destructiveTimestamps.filter((t) => t > cutoff);
|
|
30338
|
-
if (destructiveTimestamps.length >= o.rateLimitPerMinute) {
|
|
30339
|
-
throw new GuardrailError(`Destructive call rate limit exceeded (${o.rateLimitPerMinute}/min). Wait and retry.`);
|
|
30340
|
-
}
|
|
30341
|
-
destructiveTimestamps.push(now);
|
|
30342
|
-
}
|
|
30343
|
-
function isStoreAllowlistActive() {
|
|
30344
|
-
return loadOptions().allowedStoreIds !== null;
|
|
30345
|
-
}
|
|
30346
|
-
function isPresent(value) {
|
|
30347
|
-
if (value === void 0 || value === null) return false;
|
|
30348
|
-
if (typeof value === "string" && value === "") return false;
|
|
30349
|
-
if (Array.isArray(value) && value.length === 0) return false;
|
|
30350
|
-
return true;
|
|
30351
|
-
}
|
|
30352
|
-
function checkStoreScopedToolInput(tool, input) {
|
|
30353
|
-
const toolAcceptsStoreId = "storeId" in tool.inputSchema.shape;
|
|
30354
|
-
if (toolAcceptsStoreId) {
|
|
30355
|
-
const raw = input.storeId;
|
|
30356
|
-
if (raw !== void 0 && raw !== null && raw !== "") {
|
|
30357
|
-
checkStoreAllowed(String(raw));
|
|
30358
|
-
} else if (isStoreAllowlistActive()) {
|
|
30359
|
-
throw new GuardrailError("storeId is required when LEMONSQUEEZY_ALLOWED_STORE_IDS is set");
|
|
30360
|
-
}
|
|
30361
|
-
}
|
|
30362
|
-
if (tool.requiredFilters && tool.requiredFilters.length > 0 && isStoreAllowlistActive()) {
|
|
30363
|
-
const anyPresent = tool.requiredFilters.some((key) => isPresent(input[key]));
|
|
30364
|
-
if (!anyPresent) {
|
|
30365
|
-
throw new GuardrailError(
|
|
30366
|
-
`At least one of [${tool.requiredFilters.join(", ")}] is required when LEMONSQUEEZY_ALLOWED_STORE_IDS is set`
|
|
30367
|
-
);
|
|
30368
|
-
}
|
|
30369
|
-
}
|
|
30370
|
-
}
|
|
30371
|
-
function isDestructiveCall(tool, input) {
|
|
30372
|
-
if (typeof tool.isDestructive === "function") return tool.isDestructive(input);
|
|
30373
|
-
return tool.annotations?.destructiveHint === true;
|
|
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
|
-
}
|
|
30399
|
-
|
|
30400
30203
|
// src/logger.ts
|
|
30401
30204
|
function getLogLevel() {
|
|
30402
30205
|
const v = process.env.LEMONSQUEEZY_LOG;
|
|
@@ -30424,6 +30227,15 @@ function shouldLog(entry, level) {
|
|
|
30424
30227
|
return isErrorEntry(entry);
|
|
30425
30228
|
}
|
|
30426
30229
|
}
|
|
30230
|
+
function safeString(value) {
|
|
30231
|
+
if (value === void 0) return void 0;
|
|
30232
|
+
if (typeof value === "string") return value;
|
|
30233
|
+
try {
|
|
30234
|
+
return String(value);
|
|
30235
|
+
} catch {
|
|
30236
|
+
return "[unrepresentable]";
|
|
30237
|
+
}
|
|
30238
|
+
}
|
|
30427
30239
|
function logEvent(entry) {
|
|
30428
30240
|
const level = getLogLevel();
|
|
30429
30241
|
if (!shouldLog(entry, level)) return;
|
|
@@ -30445,7 +30257,7 @@ function logEvent(entry) {
|
|
|
30445
30257
|
latency_ms: entry.latency_ms,
|
|
30446
30258
|
request_id: entry.request_id,
|
|
30447
30259
|
audit: entry.audit,
|
|
30448
|
-
error: entry.error,
|
|
30260
|
+
error: safeString(entry.error),
|
|
30449
30261
|
log_error: "inputs_not_serializable"
|
|
30450
30262
|
});
|
|
30451
30263
|
process.stderr.write(`${fallback}
|
|
@@ -30454,41 +30266,6 @@ function logEvent(entry) {
|
|
|
30454
30266
|
}
|
|
30455
30267
|
}
|
|
30456
30268
|
|
|
30457
|
-
// src/redact.ts
|
|
30458
|
-
var SECRET_KEY_RE = /^(secret|password|token|api[_-]?key|bearer|authorization|signing[_-]?secret)$/i;
|
|
30459
|
-
var REDACTED = "[REDACTED]";
|
|
30460
|
-
var CIRCULAR = "[CIRCULAR]";
|
|
30461
|
-
var MAX_DEPTH = 32;
|
|
30462
|
-
function isPlainObject3(value) {
|
|
30463
|
-
if (value === null || typeof value !== "object") return false;
|
|
30464
|
-
const proto = Object.getPrototypeOf(value);
|
|
30465
|
-
return proto === Object.prototype || proto === null;
|
|
30466
|
-
}
|
|
30467
|
-
function redactInner(value, visited, depth) {
|
|
30468
|
-
if (depth > MAX_DEPTH) return CIRCULAR;
|
|
30469
|
-
if (value === null || typeof value !== "object") return value;
|
|
30470
|
-
if (Array.isArray(value)) {
|
|
30471
|
-
if (visited.has(value)) return CIRCULAR;
|
|
30472
|
-
visited.add(value);
|
|
30473
|
-
return value.map((item) => redactInner(item, visited, depth + 1));
|
|
30474
|
-
}
|
|
30475
|
-
if (!isPlainObject3(value)) return value;
|
|
30476
|
-
if (visited.has(value)) return CIRCULAR;
|
|
30477
|
-
visited.add(value);
|
|
30478
|
-
const out = {};
|
|
30479
|
-
for (const [key, val] of Object.entries(value)) {
|
|
30480
|
-
if (SECRET_KEY_RE.test(key)) {
|
|
30481
|
-
out[key] = REDACTED;
|
|
30482
|
-
} else {
|
|
30483
|
-
out[key] = redactInner(val, visited, depth + 1);
|
|
30484
|
-
}
|
|
30485
|
-
}
|
|
30486
|
-
return out;
|
|
30487
|
-
}
|
|
30488
|
-
function redactSecrets(input) {
|
|
30489
|
-
return redactInner(input, /* @__PURE__ */ new WeakSet(), 0);
|
|
30490
|
-
}
|
|
30491
|
-
|
|
30492
30269
|
// src/retry.ts
|
|
30493
30270
|
var REQUEST_TIMEOUT_MS = 3e4;
|
|
30494
30271
|
var DEFAULT_RETRY_WAIT_MS = 1e3;
|
|
@@ -30595,11 +30372,15 @@ async function fetchWithRetry(url2, init, opts) {
|
|
|
30595
30372
|
|
|
30596
30373
|
// src/secret.ts
|
|
30597
30374
|
import { execFile } from "node:child_process";
|
|
30375
|
+
import { createHash } from "node:crypto";
|
|
30598
30376
|
import { promisify } from "node:util";
|
|
30599
30377
|
var execFileAsync = promisify(execFile);
|
|
30600
30378
|
var CACHE_TTL_MS = 60 * 60 * 1e3;
|
|
30601
30379
|
var COMMAND_TIMEOUT_MS = 1e4;
|
|
30602
30380
|
var COMMAND_MAX_BUFFER = 64 * 1024;
|
|
30381
|
+
function fingerprintFor(mode, raw) {
|
|
30382
|
+
return createHash("sha256").update(`${mode}:${raw}`).digest("hex");
|
|
30383
|
+
}
|
|
30603
30384
|
var cached2 = null;
|
|
30604
30385
|
var testModeAnnounced = false;
|
|
30605
30386
|
function announceTestModeOnce() {
|
|
@@ -30652,7 +30433,7 @@ function intoCache(fingerprint, key) {
|
|
|
30652
30433
|
async function loadApiKey() {
|
|
30653
30434
|
const cmdStr = process.env.LEMONSQUEEZY_API_KEY_COMMAND;
|
|
30654
30435
|
if (cmdStr && cmdStr.trim() !== "") {
|
|
30655
|
-
const fingerprint2 =
|
|
30436
|
+
const fingerprint2 = fingerprintFor("cmd", cmdStr);
|
|
30656
30437
|
const hit2 = fromCache(fingerprint2);
|
|
30657
30438
|
if (hit2 !== null) return hit2;
|
|
30658
30439
|
const { command, args } = parseCommand(cmdStr);
|
|
@@ -30674,7 +30455,7 @@ async function loadApiKey() {
|
|
|
30674
30455
|
}
|
|
30675
30456
|
const testRaw = process.env.LEMONSQUEEZY_TEST_API_KEY;
|
|
30676
30457
|
if (testRaw && testRaw.trim() !== "") {
|
|
30677
|
-
const fingerprint2 =
|
|
30458
|
+
const fingerprint2 = fingerprintFor("test", testRaw);
|
|
30678
30459
|
const hit2 = fromCache(fingerprint2);
|
|
30679
30460
|
if (hit2 !== null) {
|
|
30680
30461
|
announceTestModeOnce();
|
|
@@ -30691,7 +30472,7 @@ async function loadApiKey() {
|
|
|
30691
30472
|
if (raw.trim() === "") {
|
|
30692
30473
|
throw new Error("LEMONSQUEEZY_API_KEY is set but empty. Provide a valid API key.");
|
|
30693
30474
|
}
|
|
30694
|
-
const fingerprint =
|
|
30475
|
+
const fingerprint = fingerprintFor("env", raw);
|
|
30695
30476
|
const hit = fromCache(fingerprint);
|
|
30696
30477
|
if (hit !== null) return hit;
|
|
30697
30478
|
intoCache(fingerprint, raw);
|
|
@@ -31680,6 +31461,186 @@ var orderItemTools = [
|
|
|
31680
31461
|
}
|
|
31681
31462
|
];
|
|
31682
31463
|
|
|
31464
|
+
// src/guardrails.ts
|
|
31465
|
+
var GuardrailError = class extends Error {
|
|
31466
|
+
constructor(message) {
|
|
31467
|
+
super(message);
|
|
31468
|
+
this.name = "GuardrailError";
|
|
31469
|
+
}
|
|
31470
|
+
};
|
|
31471
|
+
var AUTHORITY_CLASSES = ["read", "pii", "mutate", "money", "recurring", "key", "webhook"];
|
|
31472
|
+
function isAuthorityClass(s) {
|
|
31473
|
+
return AUTHORITY_CLASSES.includes(s);
|
|
31474
|
+
}
|
|
31475
|
+
var cachedOptions = null;
|
|
31476
|
+
var destructiveTimestamps = [];
|
|
31477
|
+
var classTimestamps = /* @__PURE__ */ new Map();
|
|
31478
|
+
function readNumber(name, raw) {
|
|
31479
|
+
if (raw === void 0 || raw.trim() === "") return null;
|
|
31480
|
+
const n = Number(raw);
|
|
31481
|
+
if (!Number.isFinite(n) || n < 0) {
|
|
31482
|
+
throw new Error(`${name} must be a non-negative number (got ${JSON.stringify(raw)})`);
|
|
31483
|
+
}
|
|
31484
|
+
return n;
|
|
31485
|
+
}
|
|
31486
|
+
function parseDisabledClasses(raw) {
|
|
31487
|
+
if (!raw || raw.trim() === "") return null;
|
|
31488
|
+
const out = /* @__PURE__ */ new Set();
|
|
31489
|
+
for (const part of raw.split(",")) {
|
|
31490
|
+
const cls = part.trim();
|
|
31491
|
+
if (!cls) continue;
|
|
31492
|
+
if (!isAuthorityClass(cls)) {
|
|
31493
|
+
throw new Error(
|
|
31494
|
+
`LEMONSQUEEZY_DISABLE_CLASSES contains unknown class ${JSON.stringify(cls)} (expected one of: ${AUTHORITY_CLASSES.join(", ")})`
|
|
31495
|
+
);
|
|
31496
|
+
}
|
|
31497
|
+
out.add(cls);
|
|
31498
|
+
}
|
|
31499
|
+
return out.size > 0 ? out : null;
|
|
31500
|
+
}
|
|
31501
|
+
function parseClassRateLimits(raw) {
|
|
31502
|
+
if (!raw || raw.trim() === "") return null;
|
|
31503
|
+
const out = /* @__PURE__ */ new Map();
|
|
31504
|
+
for (const part of raw.split(",")) {
|
|
31505
|
+
const segment = part.trim();
|
|
31506
|
+
if (!segment) continue;
|
|
31507
|
+
const colon = segment.indexOf(":");
|
|
31508
|
+
if (colon < 0) {
|
|
31509
|
+
throw new Error(
|
|
31510
|
+
`LEMONSQUEEZY_RATE_LIMIT_PER_CLASS entry missing colon: ${JSON.stringify(segment)} (expected class:N or class:N/h)`
|
|
31511
|
+
);
|
|
31512
|
+
}
|
|
31513
|
+
const cls = segment.slice(0, colon).trim();
|
|
31514
|
+
if (!isAuthorityClass(cls)) {
|
|
31515
|
+
throw new Error(
|
|
31516
|
+
`LEMONSQUEEZY_RATE_LIMIT_PER_CLASS contains unknown class ${JSON.stringify(cls)} (expected one of: ${AUTHORITY_CLASSES.join(", ")})`
|
|
31517
|
+
);
|
|
31518
|
+
}
|
|
31519
|
+
const specRaw = segment.slice(colon + 1).trim();
|
|
31520
|
+
const slash = specRaw.indexOf("/");
|
|
31521
|
+
const numPart = slash >= 0 ? specRaw.slice(0, slash).trim() : specRaw;
|
|
31522
|
+
const unitPart = slash >= 0 ? specRaw.slice(slash + 1).trim().toLowerCase() : "m";
|
|
31523
|
+
const n = Number(numPart);
|
|
31524
|
+
if (!Number.isFinite(n) || n < 0 || numPart === "") {
|
|
31525
|
+
throw new Error(`LEMONSQUEEZY_RATE_LIMIT_PER_CLASS entry has invalid number: ${JSON.stringify(segment)}`);
|
|
31526
|
+
}
|
|
31527
|
+
let windowMs;
|
|
31528
|
+
if (unitPart === "m") windowMs = 6e4;
|
|
31529
|
+
else if (unitPart === "h") windowMs = 36e5;
|
|
31530
|
+
else {
|
|
31531
|
+
throw new Error(
|
|
31532
|
+
`LEMONSQUEEZY_RATE_LIMIT_PER_CLASS entry has invalid unit (expected m or h): ${JSON.stringify(segment)}`
|
|
31533
|
+
);
|
|
31534
|
+
}
|
|
31535
|
+
out.set(cls, { limit: n, windowMs });
|
|
31536
|
+
}
|
|
31537
|
+
return out.size > 0 ? out : null;
|
|
31538
|
+
}
|
|
31539
|
+
function loadOptions() {
|
|
31540
|
+
if (cachedOptions) return cachedOptions;
|
|
31541
|
+
const allowed = process.env.LEMONSQUEEZY_ALLOWED_STORE_IDS;
|
|
31542
|
+
const allowedSet = allowed ? new Set(
|
|
31543
|
+
allowed.split(",").map((s) => s.trim()).filter(Boolean)
|
|
31544
|
+
) : null;
|
|
31545
|
+
cachedOptions = {
|
|
31546
|
+
allowedStoreIds: allowedSet && allowedSet.size > 0 ? allowedSet : null,
|
|
31547
|
+
maxRefundAmountCents: readNumber(
|
|
31548
|
+
"LEMONSQUEEZY_MAX_REFUND_AMOUNT_CENTS",
|
|
31549
|
+
process.env.LEMONSQUEEZY_MAX_REFUND_AMOUNT_CENTS
|
|
31550
|
+
),
|
|
31551
|
+
rateLimitPerMinute: readNumber(
|
|
31552
|
+
"LEMONSQUEEZY_DESTRUCTIVE_RATE_LIMIT",
|
|
31553
|
+
process.env.LEMONSQUEEZY_DESTRUCTIVE_RATE_LIMIT
|
|
31554
|
+
),
|
|
31555
|
+
disabledClasses: parseDisabledClasses(process.env.LEMONSQUEEZY_DISABLE_CLASSES),
|
|
31556
|
+
classRateLimits: parseClassRateLimits(process.env.LEMONSQUEEZY_RATE_LIMIT_PER_CLASS)
|
|
31557
|
+
};
|
|
31558
|
+
return cachedOptions;
|
|
31559
|
+
}
|
|
31560
|
+
function checkStoreAllowed(storeId) {
|
|
31561
|
+
if (!storeId) return;
|
|
31562
|
+
const o = loadOptions();
|
|
31563
|
+
if (!o.allowedStoreIds) return;
|
|
31564
|
+
if (!o.allowedStoreIds.has(String(storeId))) {
|
|
31565
|
+
throw new GuardrailError(`Store ID ${storeId} is not in LEMONSQUEEZY_ALLOWED_STORE_IDS allowlist`);
|
|
31566
|
+
}
|
|
31567
|
+
}
|
|
31568
|
+
function checkRefundAmount(cents) {
|
|
31569
|
+
const o = loadOptions();
|
|
31570
|
+
if (o.maxRefundAmountCents === null) return;
|
|
31571
|
+
if (cents > o.maxRefundAmountCents) {
|
|
31572
|
+
throw new GuardrailError(
|
|
31573
|
+
`Refund amount ${cents} cents exceeds LEMONSQUEEZY_MAX_REFUND_AMOUNT_CENTS (${o.maxRefundAmountCents})`
|
|
31574
|
+
);
|
|
31575
|
+
}
|
|
31576
|
+
}
|
|
31577
|
+
function checkDestructiveRateLimit(now = Date.now()) {
|
|
31578
|
+
const o = loadOptions();
|
|
31579
|
+
if (o.rateLimitPerMinute === null) return;
|
|
31580
|
+
const cutoff = now - 6e4;
|
|
31581
|
+
destructiveTimestamps = destructiveTimestamps.filter((t) => t > cutoff);
|
|
31582
|
+
if (destructiveTimestamps.length >= o.rateLimitPerMinute) {
|
|
31583
|
+
throw new GuardrailError(`Destructive call rate limit exceeded (${o.rateLimitPerMinute}/min). Wait and retry.`);
|
|
31584
|
+
}
|
|
31585
|
+
destructiveTimestamps.push(now);
|
|
31586
|
+
}
|
|
31587
|
+
function isStoreAllowlistActive() {
|
|
31588
|
+
return loadOptions().allowedStoreIds !== null;
|
|
31589
|
+
}
|
|
31590
|
+
function isPresent(value) {
|
|
31591
|
+
if (value === void 0 || value === null) return false;
|
|
31592
|
+
if (typeof value === "string" && value === "") return false;
|
|
31593
|
+
if (Array.isArray(value) && value.length === 0) return false;
|
|
31594
|
+
return true;
|
|
31595
|
+
}
|
|
31596
|
+
function checkStoreScopedToolInput(tool, input) {
|
|
31597
|
+
const toolAcceptsStoreId = "storeId" in tool.inputSchema.shape;
|
|
31598
|
+
if (toolAcceptsStoreId) {
|
|
31599
|
+
const raw = input.storeId;
|
|
31600
|
+
if (raw !== void 0 && raw !== null && raw !== "") {
|
|
31601
|
+
checkStoreAllowed(String(raw));
|
|
31602
|
+
} else if (isStoreAllowlistActive()) {
|
|
31603
|
+
throw new GuardrailError("storeId is required when LEMONSQUEEZY_ALLOWED_STORE_IDS is set");
|
|
31604
|
+
}
|
|
31605
|
+
}
|
|
31606
|
+
if (tool.requiredFilters && tool.requiredFilters.length > 0 && isStoreAllowlistActive()) {
|
|
31607
|
+
const anyPresent = tool.requiredFilters.some((key) => isPresent(input[key]));
|
|
31608
|
+
if (!anyPresent) {
|
|
31609
|
+
throw new GuardrailError(
|
|
31610
|
+
`At least one of [${tool.requiredFilters.join(", ")}] is required when LEMONSQUEEZY_ALLOWED_STORE_IDS is set`
|
|
31611
|
+
);
|
|
31612
|
+
}
|
|
31613
|
+
}
|
|
31614
|
+
}
|
|
31615
|
+
function isDestructiveCall(tool, input) {
|
|
31616
|
+
if (typeof tool.isDestructive === "function") return tool.isDestructive(input);
|
|
31617
|
+
return tool.annotations?.destructiveHint === true;
|
|
31618
|
+
}
|
|
31619
|
+
function checkClassAllowed(cls) {
|
|
31620
|
+
const o = loadOptions();
|
|
31621
|
+
if (!o.disabledClasses) return;
|
|
31622
|
+
if (o.disabledClasses.has(cls)) {
|
|
31623
|
+
throw new GuardrailError(`Tool authority class ${JSON.stringify(cls)} is disabled by LEMONSQUEEZY_DISABLE_CLASSES`);
|
|
31624
|
+
}
|
|
31625
|
+
}
|
|
31626
|
+
function checkClassRateLimit(cls, now = Date.now()) {
|
|
31627
|
+
const o = loadOptions();
|
|
31628
|
+
if (!o.classRateLimits) return;
|
|
31629
|
+
const spec = o.classRateLimits.get(cls);
|
|
31630
|
+
if (!spec) return;
|
|
31631
|
+
const cutoff = now - spec.windowMs;
|
|
31632
|
+
const list = (classTimestamps.get(cls) ?? []).filter((t) => t > cutoff);
|
|
31633
|
+
if (list.length >= spec.limit) {
|
|
31634
|
+
const unit = spec.windowMs === 6e4 ? "min" : spec.windowMs === 36e5 ? "hour" : `${spec.windowMs}ms`;
|
|
31635
|
+
classTimestamps.set(cls, list);
|
|
31636
|
+
throw new GuardrailError(
|
|
31637
|
+
`Class ${JSON.stringify(cls)} rate limit exceeded (${spec.limit}/${unit}). Wait and retry.`
|
|
31638
|
+
);
|
|
31639
|
+
}
|
|
31640
|
+
list.push(now);
|
|
31641
|
+
classTimestamps.set(cls, list);
|
|
31642
|
+
}
|
|
31643
|
+
|
|
31683
31644
|
// src/tools/orders.ts
|
|
31684
31645
|
var orderTools = [
|
|
31685
31646
|
{
|
|
@@ -32495,8 +32456,168 @@ var webhookTools = [
|
|
|
32495
32456
|
}
|
|
32496
32457
|
];
|
|
32497
32458
|
|
|
32459
|
+
// src/audit-buffer.ts
|
|
32460
|
+
var DEFAULT_CAP = 1e3;
|
|
32461
|
+
var backing = new Array(DEFAULT_CAP);
|
|
32462
|
+
var cap = DEFAULT_CAP;
|
|
32463
|
+
var next = 0;
|
|
32464
|
+
var size = 0;
|
|
32465
|
+
function pushAuditEntry(entry) {
|
|
32466
|
+
backing[next] = entry;
|
|
32467
|
+
next = (next + 1) % cap;
|
|
32468
|
+
if (size < cap) size += 1;
|
|
32469
|
+
}
|
|
32470
|
+
function readAuditEntries(limit) {
|
|
32471
|
+
if (limit !== void 0 && limit <= 0) return [];
|
|
32472
|
+
const wanted = limit === void 0 ? size : Math.min(limit, size);
|
|
32473
|
+
if (wanted === 0) return [];
|
|
32474
|
+
const out = new Array(wanted);
|
|
32475
|
+
let cursor = (next - 1 + cap) % cap;
|
|
32476
|
+
for (let i = 0; i < wanted; i++) {
|
|
32477
|
+
out[i] = backing[cursor];
|
|
32478
|
+
cursor = (cursor - 1 + cap) % cap;
|
|
32479
|
+
}
|
|
32480
|
+
return out;
|
|
32481
|
+
}
|
|
32482
|
+
|
|
32483
|
+
// src/redact.ts
|
|
32484
|
+
var SECRET_KEY_RE = /^(secret|password|token|api[_-]?key|bearer|authorization|signing[_-]?secret|private[_-]?key|pin|ssn|social[_-]?security[_-]?number|credit[_-]?card|card[_-]?number|cvv|cvc)$/i;
|
|
32485
|
+
var JWT_VALUE_RE = /^eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]{4,}\.[A-Za-z0-9_-]{4,}$/;
|
|
32486
|
+
var REDACTED = "[REDACTED]";
|
|
32487
|
+
var CIRCULAR = "[CIRCULAR]";
|
|
32488
|
+
var MAX_DEPTH = 32;
|
|
32489
|
+
function isPlainObject3(value) {
|
|
32490
|
+
if (value === null || typeof value !== "object") return false;
|
|
32491
|
+
const proto = Object.getPrototypeOf(value);
|
|
32492
|
+
return proto === Object.prototype || proto === null;
|
|
32493
|
+
}
|
|
32494
|
+
function looksLikeBearerToken(value) {
|
|
32495
|
+
if (typeof value !== "string") return false;
|
|
32496
|
+
if (value.length < 20) return false;
|
|
32497
|
+
return JWT_VALUE_RE.test(value);
|
|
32498
|
+
}
|
|
32499
|
+
function redactInner(value, visited, depth) {
|
|
32500
|
+
if (depth > MAX_DEPTH) return CIRCULAR;
|
|
32501
|
+
if (typeof value === "string" && looksLikeBearerToken(value)) return REDACTED;
|
|
32502
|
+
if (value === null || typeof value !== "object") return value;
|
|
32503
|
+
if (Array.isArray(value)) {
|
|
32504
|
+
if (visited.has(value)) return CIRCULAR;
|
|
32505
|
+
visited.add(value);
|
|
32506
|
+
return value.map((item) => redactInner(item, visited, depth + 1));
|
|
32507
|
+
}
|
|
32508
|
+
if (!isPlainObject3(value)) return value;
|
|
32509
|
+
if (visited.has(value)) return CIRCULAR;
|
|
32510
|
+
visited.add(value);
|
|
32511
|
+
const out = {};
|
|
32512
|
+
for (const [key, val] of Object.entries(value)) {
|
|
32513
|
+
if (SECRET_KEY_RE.test(key)) {
|
|
32514
|
+
out[key] = REDACTED;
|
|
32515
|
+
} else {
|
|
32516
|
+
out[key] = redactInner(val, visited, depth + 1);
|
|
32517
|
+
}
|
|
32518
|
+
}
|
|
32519
|
+
return out;
|
|
32520
|
+
}
|
|
32521
|
+
function redactSecrets(input) {
|
|
32522
|
+
return redactInner(input, /* @__PURE__ */ new WeakSet(), 0);
|
|
32523
|
+
}
|
|
32524
|
+
|
|
32525
|
+
// src/wrapper.ts
|
|
32526
|
+
function createToolHandler(tool) {
|
|
32527
|
+
return async (input) => {
|
|
32528
|
+
if (input === null || typeof input !== "object") {
|
|
32529
|
+
const contractError = `createToolHandler expected an object input for tool "${tool.name}", got ${input === null ? "null" : typeof input}. The MCP SDK should have rejected this before reaching the handler.`;
|
|
32530
|
+
logEvent({
|
|
32531
|
+
event: "tool_call",
|
|
32532
|
+
tool: tool.name,
|
|
32533
|
+
status: "exception",
|
|
32534
|
+
latency_ms: 0,
|
|
32535
|
+
error: contractError
|
|
32536
|
+
});
|
|
32537
|
+
return {
|
|
32538
|
+
content: [{ type: "text", text: `Error: ${contractError}` }],
|
|
32539
|
+
isError: true
|
|
32540
|
+
};
|
|
32541
|
+
}
|
|
32542
|
+
const inputAsRecord = input;
|
|
32543
|
+
const toolAsRecord = tool;
|
|
32544
|
+
const isDestructive = isDestructiveCall(toolAsRecord, inputAsRecord);
|
|
32545
|
+
const start = Date.now();
|
|
32546
|
+
try {
|
|
32547
|
+
checkClassAllowed(tool.authorityClass);
|
|
32548
|
+
checkClassRateLimit(tool.authorityClass);
|
|
32549
|
+
if (isDestructive) checkDestructiveRateLimit();
|
|
32550
|
+
checkStoreScopedToolInput(toolAsRecord, inputAsRecord);
|
|
32551
|
+
const response = await tool.handler(input);
|
|
32552
|
+
const latency_ms = Date.now() - start;
|
|
32553
|
+
const successEntry = {
|
|
32554
|
+
event: "tool_call",
|
|
32555
|
+
tool: tool.name,
|
|
32556
|
+
status: response.ok ? "ok" : "error",
|
|
32557
|
+
latency_ms,
|
|
32558
|
+
request_id: response.requestId,
|
|
32559
|
+
error: response.ok ? void 0 : response.error,
|
|
32560
|
+
audit: isDestructive ? true : void 0,
|
|
32561
|
+
inputs: isDestructive ? redactSecrets(input) : void 0
|
|
32562
|
+
};
|
|
32563
|
+
logEvent(successEntry);
|
|
32564
|
+
if (isDestructive) {
|
|
32565
|
+
pushAuditEntry({ ts: (/* @__PURE__ */ new Date()).toISOString(), ...successEntry });
|
|
32566
|
+
}
|
|
32567
|
+
if (!response.ok) {
|
|
32568
|
+
return {
|
|
32569
|
+
content: [
|
|
32570
|
+
{
|
|
32571
|
+
type: "text",
|
|
32572
|
+
text: `Error: ${response.error || "Unknown error"}`
|
|
32573
|
+
}
|
|
32574
|
+
],
|
|
32575
|
+
isError: true
|
|
32576
|
+
};
|
|
32577
|
+
}
|
|
32578
|
+
const text = JSON.stringify(response.data ?? { success: true }, null, 2);
|
|
32579
|
+
return {
|
|
32580
|
+
content: [{ type: "text", text }]
|
|
32581
|
+
};
|
|
32582
|
+
} catch (err) {
|
|
32583
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
32584
|
+
const latency_ms = Date.now() - start;
|
|
32585
|
+
const errorEntry = {
|
|
32586
|
+
event: "tool_call",
|
|
32587
|
+
tool: tool.name,
|
|
32588
|
+
status: err instanceof GuardrailError ? "guardrail_block" : "exception",
|
|
32589
|
+
latency_ms,
|
|
32590
|
+
error: message,
|
|
32591
|
+
audit: isDestructive ? true : void 0,
|
|
32592
|
+
inputs: isDestructive ? redactSecrets(input) : void 0
|
|
32593
|
+
};
|
|
32594
|
+
logEvent(errorEntry);
|
|
32595
|
+
if (isDestructive) {
|
|
32596
|
+
pushAuditEntry({ ts: (/* @__PURE__ */ new Date()).toISOString(), ...errorEntry });
|
|
32597
|
+
}
|
|
32598
|
+
return {
|
|
32599
|
+
content: [{ type: "text", text: `Error: ${message}` }],
|
|
32600
|
+
isError: true
|
|
32601
|
+
};
|
|
32602
|
+
}
|
|
32603
|
+
};
|
|
32604
|
+
}
|
|
32605
|
+
function readAuditLogResource(uri) {
|
|
32606
|
+
const entries = readAuditEntries();
|
|
32607
|
+
const text = entries.map((e) => JSON.stringify(e)).join("\n");
|
|
32608
|
+
return {
|
|
32609
|
+
contents: [
|
|
32610
|
+
{
|
|
32611
|
+
uri: uri.href,
|
|
32612
|
+
mimeType: "application/x-ndjson",
|
|
32613
|
+
text
|
|
32614
|
+
}
|
|
32615
|
+
]
|
|
32616
|
+
};
|
|
32617
|
+
}
|
|
32618
|
+
|
|
32498
32619
|
// src/index.ts
|
|
32499
|
-
var version2 = true ? "0.9.
|
|
32620
|
+
var version2 = true ? "0.9.2" : (await null).createRequire(import.meta.url)("../package.json").version;
|
|
32500
32621
|
var subcommand = process.argv[2];
|
|
32501
32622
|
if (subcommand === "version" || subcommand === "--version") {
|
|
32502
32623
|
console.log(version2);
|
|
@@ -32530,74 +32651,7 @@ var server = new McpServer({
|
|
|
32530
32651
|
version: version2
|
|
32531
32652
|
});
|
|
32532
32653
|
for (const tool of allTools) {
|
|
32533
|
-
server.tool(
|
|
32534
|
-
tool.name,
|
|
32535
|
-
tool.description,
|
|
32536
|
-
tool.inputSchema.shape,
|
|
32537
|
-
tool.annotations,
|
|
32538
|
-
async (input) => {
|
|
32539
|
-
const isDestructive = isDestructiveCall(tool, input);
|
|
32540
|
-
const start = Date.now();
|
|
32541
|
-
try {
|
|
32542
|
-
checkClassAllowed(tool.authorityClass);
|
|
32543
|
-
checkClassRateLimit(tool.authorityClass);
|
|
32544
|
-
if (isDestructive) checkDestructiveRateLimit();
|
|
32545
|
-
checkStoreScopedToolInput(tool, input);
|
|
32546
|
-
const result = await tool.handler(input);
|
|
32547
|
-
const response = result;
|
|
32548
|
-
const latency_ms = Date.now() - start;
|
|
32549
|
-
const successEntry = {
|
|
32550
|
-
event: "tool_call",
|
|
32551
|
-
tool: tool.name,
|
|
32552
|
-
status: response.ok ? "ok" : "error",
|
|
32553
|
-
latency_ms,
|
|
32554
|
-
request_id: response.requestId,
|
|
32555
|
-
error: response.ok ? void 0 : response.error,
|
|
32556
|
-
audit: isDestructive ? true : void 0,
|
|
32557
|
-
inputs: isDestructive ? redactSecrets(input) : void 0
|
|
32558
|
-
};
|
|
32559
|
-
logEvent(successEntry);
|
|
32560
|
-
if (isDestructive) {
|
|
32561
|
-
pushAuditEntry({ ts: (/* @__PURE__ */ new Date()).toISOString(), ...successEntry });
|
|
32562
|
-
}
|
|
32563
|
-
if (!response.ok) {
|
|
32564
|
-
return {
|
|
32565
|
-
content: [
|
|
32566
|
-
{
|
|
32567
|
-
type: "text",
|
|
32568
|
-
text: `Error: ${response.error || "Unknown error"}`
|
|
32569
|
-
}
|
|
32570
|
-
],
|
|
32571
|
-
isError: true
|
|
32572
|
-
};
|
|
32573
|
-
}
|
|
32574
|
-
const text = JSON.stringify(response.data ?? { success: true }, null, 2);
|
|
32575
|
-
return {
|
|
32576
|
-
content: [{ type: "text", text }]
|
|
32577
|
-
};
|
|
32578
|
-
} catch (err) {
|
|
32579
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
32580
|
-
const latency_ms = Date.now() - start;
|
|
32581
|
-
const errorEntry = {
|
|
32582
|
-
event: "tool_call",
|
|
32583
|
-
tool: tool.name,
|
|
32584
|
-
status: err instanceof GuardrailError ? "guardrail_block" : "exception",
|
|
32585
|
-
latency_ms,
|
|
32586
|
-
error: message,
|
|
32587
|
-
audit: isDestructive ? true : void 0,
|
|
32588
|
-
inputs: isDestructive ? redactSecrets(input) : void 0
|
|
32589
|
-
};
|
|
32590
|
-
logEvent(errorEntry);
|
|
32591
|
-
if (isDestructive) {
|
|
32592
|
-
pushAuditEntry({ ts: (/* @__PURE__ */ new Date()).toISOString(), ...errorEntry });
|
|
32593
|
-
}
|
|
32594
|
-
return {
|
|
32595
|
-
content: [{ type: "text", text: `Error: ${message}` }],
|
|
32596
|
-
isError: true
|
|
32597
|
-
};
|
|
32598
|
-
}
|
|
32599
|
-
}
|
|
32600
|
-
);
|
|
32654
|
+
server.tool(tool.name, tool.description, tool.inputSchema.shape, tool.annotations, createToolHandler(tool));
|
|
32601
32655
|
}
|
|
32602
32656
|
server.resource(
|
|
32603
32657
|
"Recent destructive-call audit log",
|
|
@@ -32606,19 +32660,7 @@ server.resource(
|
|
|
32606
32660
|
description: "The most recent destructive tool calls and their outcomes (rate limit, refund cap, etc.). Bounded ring buffer; resets on server restart.",
|
|
32607
32661
|
mimeType: "application/x-ndjson"
|
|
32608
32662
|
},
|
|
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
|
-
}
|
|
32663
|
+
async (uri) => readAuditLogResource(uri)
|
|
32622
32664
|
);
|
|
32623
32665
|
var transport = new StdioServerTransport();
|
|
32624
32666
|
await server.connect(transport);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yawlabs/lemonsqueezy-mcp",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.2",
|
|
4
4
|
"mcpName": "io.github.YawLabs/lemonsqueezy-mcp",
|
|
5
5
|
"description": "LemonSqueezy MCP server for managing your store from AI assistants",
|
|
6
6
|
"license": "MIT",
|
|
@@ -35,6 +35,8 @@
|
|
|
35
35
|
"test:integration": "npm run build && node --test dist/integration/*.test.js",
|
|
36
36
|
"lint": "biome check src/",
|
|
37
37
|
"lint:fix": "biome check --write src/",
|
|
38
|
+
"gen:containerfile": "node scripts/sync-containerfile.mjs",
|
|
39
|
+
"check:containerfile": "node scripts/sync-containerfile.mjs --check",
|
|
38
40
|
"prepublishOnly": "npm run build"
|
|
39
41
|
},
|
|
40
42
|
"dependencies": {},
|