@yawlabs/lemonsqueezy-mcp 0.7.1 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +76 -4
- package/dist/index.js +217 -60
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -8,6 +8,37 @@ MCP server for the [LemonSqueezy](https://lemonsqueezy.com) API. Manage your sto
|
|
|
8
8
|
npx @yawlabs/lemonsqueezy-mcp
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
+
Or one-click install via Smithery:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npx -y @smithery/cli install @yawlabs/lemonsqueezy-mcp --client claude
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Smithery prompts for your env vars (API key, optional guardrails) and writes the config into your client for you.
|
|
18
|
+
|
|
19
|
+
## What it looks like
|
|
20
|
+
|
|
21
|
+
Once configured, you can ask your AI assistant store-management questions in plain English and it routes them through the MCP tools:
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
You: How much did we make from the "Pro Annual" plan last month?
|
|
25
|
+
Claude: [calls ls_list_subscriptions, ls_get_variant, ls_list_subscription_invoices]
|
|
26
|
+
Pro Annual brought in $14,280 across 84 active subscriptions in April.
|
|
27
|
+
Three of those were upgrades from monthly; none churned.
|
|
28
|
+
|
|
29
|
+
You: Refund order #LS-1234 in full.
|
|
30
|
+
Claude: [calls ls_get_order to fetch the total, then ls_refund_order with amount = total]
|
|
31
|
+
Refunded $99.00 against order LS-1234. The customer's card will see the
|
|
32
|
+
credit in 5-10 business days.
|
|
33
|
+
|
|
34
|
+
You: Disable license key abc-123 for the customer who reported abuse.
|
|
35
|
+
Claude: [calls ls_list_license_keys to find the ID, then ls_update_license_key with disabled: true]
|
|
36
|
+
License key disabled. Their existing activations will fail validation
|
|
37
|
+
on the next check.
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
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.
|
|
41
|
+
|
|
11
42
|
## Setup
|
|
12
43
|
|
|
13
44
|
Set your LemonSqueezy API key as an environment variable:
|
|
@@ -18,6 +49,17 @@ export LEMONSQUEEZY_API_KEY="your-api-key"
|
|
|
18
49
|
|
|
19
50
|
Get your API key from your [LemonSqueezy dashboard](https://app.lemonsqueezy.com/settings/api).
|
|
20
51
|
|
|
52
|
+
### Docker
|
|
53
|
+
|
|
54
|
+
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).
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
docker build -t yawlabs/lemonsqueezy-mcp .
|
|
58
|
+
docker run --rm -i -e LEMONSQUEEZY_API_KEY="your-api-key" yawlabs/lemonsqueezy-mcp
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
A byte-identical `Containerfile` is also provided for Podman users.
|
|
62
|
+
|
|
21
63
|
### Claude Code
|
|
22
64
|
|
|
23
65
|
Add to your MCP config:
|
|
@@ -174,6 +216,7 @@ All configuration is via environment variables. Only `LEMONSQUEEZY_API_KEY` (or
|
|
|
174
216
|
| --- | --- |
|
|
175
217
|
| `LEMONSQUEEZY_API_KEY` | LemonSqueezy API token. |
|
|
176
218
|
| `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. |
|
|
219
|
+
| `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
220
|
| `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
221
|
| `LEMONSQUEEZY_MAX_REFUND_AMOUNT_CENTS` | Rejects `ls_refund_order` and `ls_refund_subscription_invoice` calls above this amount. |
|
|
179
222
|
| `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. |
|
|
@@ -187,6 +230,14 @@ Each line: `{ts, event, tool?, method?, path?, status, latency_ms, request_id?,
|
|
|
187
230
|
|
|
188
231
|
HTTP errors include the upstream `X-Request-Id` when present, so support tickets to LemonSqueezy can reference the exact call.
|
|
189
232
|
|
|
233
|
+
## Resources
|
|
234
|
+
|
|
235
|
+
The server exposes one MCP Resource for clients that prefer structural retrieval over parsing stderr:
|
|
236
|
+
|
|
237
|
+
| URI | MIME type | Contents |
|
|
238
|
+
| --- | --- | --- |
|
|
239
|
+
| `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. |
|
|
240
|
+
|
|
190
241
|
## Operating the server unattended
|
|
191
242
|
|
|
192
243
|
For unattended/agentic use against a live store, we recommend:
|
|
@@ -216,15 +267,36 @@ npm run test:integration # requires LEMONSQUEEZY_TEST_API_KEY + LEMONSQUEEZY_TE
|
|
|
216
267
|
|
|
217
268
|
## Releasing
|
|
218
269
|
|
|
219
|
-
|
|
270
|
+
Two paths from a clean checkout of `main`. Both produce the same artifact (npm publish with provenance + GitHub release).
|
|
271
|
+
|
|
272
|
+
### 1. Tag-and-let-CI (preferred)
|
|
220
273
|
|
|
221
274
|
```bash
|
|
222
|
-
|
|
275
|
+
# 1. Bump version
|
|
276
|
+
npm version X.Y.Z --no-git-tag-version
|
|
277
|
+
|
|
278
|
+
# 2. Commit
|
|
279
|
+
git add package.json && git commit -m "vX.Y.Z"
|
|
280
|
+
|
|
281
|
+
# 3. Annotated tag (lightweight tags are silently skipped by --follow-tags)
|
|
282
|
+
git tag -a vX.Y.Z -m "vX.Y.Z"
|
|
283
|
+
|
|
284
|
+
# 4. Push commit + tag
|
|
285
|
+
git push origin main --follow-tags
|
|
286
|
+
|
|
287
|
+
# 5. Confirm the Release workflow fired (not just CI on the bump commit)
|
|
288
|
+
gh run list --limit 2
|
|
223
289
|
```
|
|
224
290
|
|
|
225
|
-
The
|
|
291
|
+
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.
|
|
292
|
+
|
|
293
|
+
### 2. Local end-to-end
|
|
294
|
+
|
|
295
|
+
```bash
|
|
296
|
+
./release.sh X.Y.Z
|
|
297
|
+
```
|
|
226
298
|
|
|
227
|
-
|
|
299
|
+
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
300
|
|
|
229
301
|
```bash
|
|
230
302
|
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) {
|
|
@@ -30413,6 +30517,18 @@ var CACHE_TTL_MS = 60 * 60 * 1e3;
|
|
|
30413
30517
|
var COMMAND_TIMEOUT_MS = 1e4;
|
|
30414
30518
|
var COMMAND_MAX_BUFFER = 64 * 1024;
|
|
30415
30519
|
var cached2 = null;
|
|
30520
|
+
var testModeAnnounced = false;
|
|
30521
|
+
function announceTestModeOnce() {
|
|
30522
|
+
if (testModeAnnounced) return;
|
|
30523
|
+
testModeAnnounced = true;
|
|
30524
|
+
const line = `${JSON.stringify({
|
|
30525
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
30526
|
+
event: "test_mode",
|
|
30527
|
+
message: "Using LEMONSQUEEZY_TEST_API_KEY (test mode)"
|
|
30528
|
+
})}
|
|
30529
|
+
`;
|
|
30530
|
+
process.stderr.write(line);
|
|
30531
|
+
}
|
|
30416
30532
|
function parseCommand(cmd) {
|
|
30417
30533
|
const parts = [];
|
|
30418
30534
|
let current = "";
|
|
@@ -30472,6 +30588,18 @@ async function loadApiKey() {
|
|
|
30472
30588
|
intoCache(fingerprint2, key);
|
|
30473
30589
|
return key;
|
|
30474
30590
|
}
|
|
30591
|
+
const testRaw = process.env.LEMONSQUEEZY_TEST_API_KEY;
|
|
30592
|
+
if (testRaw && testRaw.trim() !== "") {
|
|
30593
|
+
const fingerprint2 = `test:${testRaw}`;
|
|
30594
|
+
const hit2 = fromCache(fingerprint2);
|
|
30595
|
+
if (hit2 !== null) {
|
|
30596
|
+
announceTestModeOnce();
|
|
30597
|
+
return hit2;
|
|
30598
|
+
}
|
|
30599
|
+
announceTestModeOnce();
|
|
30600
|
+
intoCache(fingerprint2, testRaw);
|
|
30601
|
+
return testRaw;
|
|
30602
|
+
}
|
|
30475
30603
|
const raw = process.env.LEMONSQUEEZY_API_KEY;
|
|
30476
30604
|
if (!raw) {
|
|
30477
30605
|
throw new Error("LEMONSQUEEZY_API_KEY or LEMONSQUEEZY_API_KEY_COMMAND environment variable is required.");
|
|
@@ -32223,7 +32351,7 @@ var webhookTools = [
|
|
|
32223
32351
|
];
|
|
32224
32352
|
|
|
32225
32353
|
// src/index.ts
|
|
32226
|
-
var version2 = true ? "0.
|
|
32354
|
+
var version2 = true ? "0.8.1" : (await null).createRequire(import.meta.url)("../package.json").version;
|
|
32227
32355
|
var subcommand = process.argv[2];
|
|
32228
32356
|
if (subcommand === "version" || subcommand === "--version") {
|
|
32229
32357
|
console.log(version2);
|
|
@@ -32271,7 +32399,7 @@ for (const tool of allTools) {
|
|
|
32271
32399
|
const result = await tool.handler(input);
|
|
32272
32400
|
const response = result;
|
|
32273
32401
|
const latency_ms = Date.now() - start;
|
|
32274
|
-
|
|
32402
|
+
const successEntry = {
|
|
32275
32403
|
event: "tool_call",
|
|
32276
32404
|
tool: tool.name,
|
|
32277
32405
|
status: response.ok ? "ok" : "error",
|
|
@@ -32280,7 +32408,11 @@ for (const tool of allTools) {
|
|
|
32280
32408
|
error: response.ok ? void 0 : response.error,
|
|
32281
32409
|
audit: isDestructive ? true : void 0,
|
|
32282
32410
|
inputs: isDestructive ? redactSecrets(input) : void 0
|
|
32283
|
-
}
|
|
32411
|
+
};
|
|
32412
|
+
logEvent(successEntry);
|
|
32413
|
+
if (isDestructive) {
|
|
32414
|
+
pushAuditEntry({ ts: (/* @__PURE__ */ new Date()).toISOString(), ...successEntry });
|
|
32415
|
+
}
|
|
32284
32416
|
if (!response.ok) {
|
|
32285
32417
|
return {
|
|
32286
32418
|
content: [
|
|
@@ -32299,7 +32431,7 @@ for (const tool of allTools) {
|
|
|
32299
32431
|
} catch (err) {
|
|
32300
32432
|
const message = err instanceof Error ? err.message : String(err);
|
|
32301
32433
|
const latency_ms = Date.now() - start;
|
|
32302
|
-
|
|
32434
|
+
const errorEntry = {
|
|
32303
32435
|
event: "tool_call",
|
|
32304
32436
|
tool: tool.name,
|
|
32305
32437
|
status: err instanceof GuardrailError ? "guardrail_block" : "exception",
|
|
@@ -32307,7 +32439,11 @@ for (const tool of allTools) {
|
|
|
32307
32439
|
error: message,
|
|
32308
32440
|
audit: isDestructive ? true : void 0,
|
|
32309
32441
|
inputs: isDestructive ? redactSecrets(input) : void 0
|
|
32310
|
-
}
|
|
32442
|
+
};
|
|
32443
|
+
logEvent(errorEntry);
|
|
32444
|
+
if (isDestructive) {
|
|
32445
|
+
pushAuditEntry({ ts: (/* @__PURE__ */ new Date()).toISOString(), ...errorEntry });
|
|
32446
|
+
}
|
|
32311
32447
|
return {
|
|
32312
32448
|
content: [{ type: "text", text: `Error: ${message}` }],
|
|
32313
32449
|
isError: true
|
|
@@ -32316,6 +32452,27 @@ for (const tool of allTools) {
|
|
|
32316
32452
|
}
|
|
32317
32453
|
);
|
|
32318
32454
|
}
|
|
32455
|
+
server.resource(
|
|
32456
|
+
"Recent destructive-call audit log",
|
|
32457
|
+
"lemonsqueezy://audit-log",
|
|
32458
|
+
{
|
|
32459
|
+
description: "The most recent destructive tool calls and their outcomes (rate limit, refund cap, etc.). Bounded ring buffer; resets on server restart.",
|
|
32460
|
+
mimeType: "application/x-ndjson"
|
|
32461
|
+
},
|
|
32462
|
+
async (uri) => {
|
|
32463
|
+
const entries = readAuditEntries();
|
|
32464
|
+
const text = entries.map((e) => JSON.stringify(e)).join("\n");
|
|
32465
|
+
return {
|
|
32466
|
+
contents: [
|
|
32467
|
+
{
|
|
32468
|
+
uri: uri.href,
|
|
32469
|
+
mimeType: "application/x-ndjson",
|
|
32470
|
+
text
|
|
32471
|
+
}
|
|
32472
|
+
]
|
|
32473
|
+
};
|
|
32474
|
+
}
|
|
32475
|
+
);
|
|
32319
32476
|
var transport = new StdioServerTransport();
|
|
32320
32477
|
await server.connect(transport);
|
|
32321
32478
|
//# sourceMappingURL=index.js.map
|