@yawlabs/lemonsqueezy-mcp 0.10.8 → 0.10.10

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/CHANGELOG.md CHANGED
@@ -2,6 +2,36 @@
2
2
 
3
3
  All notable changes to `@yawlabs/lemonsqueezy-mcp` are documented here. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and versioning follows [SEMVER.md](./SEMVER.md).
4
4
 
5
+ ## [0.10.10] -- 2026-05-22
6
+
7
+ ### Fixed
8
+
9
+ - **`release.sh` no longer demands a workstation npm session for CI-publish flows.** The pre-flight `npm whoami` gate was a vestige of the local-only-deploy era (added in `53de878`); when CI publishing was restored in `e3ef87a` the gate was kept but the publish itself moved to CI's org-level `NPM_TOKEN`, so the workstation never uses that credential. Blocked the v0.10.10 release attempt with "npm is not authenticated" despite the intended publish path not needing it. Mirrors `tailscale-mcp/release.sh`, which never had the gate.
10
+
11
+ ### Security
12
+
13
+ - **Webhook `url` restricted to http/https schemes.** `ls_create_webhook` and `ls_update_webhook` previously accepted any string up to 10k chars; `z.string().url()` alone would have additionally accepted every URL-parseable scheme (`mailto:`, `file:`, `javascript:`, `ftp:`, `chrome-extension:`, ...). Anything non-http(s) stored as a webhook target is unreachable in the best case and an injection sink in the worst. The new `httpsUrlSchema` chains `.url()` with a `.refine()` enforcing `^https?://`, applied on both tools.
14
+ - **`ls_update_webhook` secret rotation gated as destructive.** Rotating the signing secret silently breaks signature verification on every receiver until they redeploy with the new value. The tool now declares an `isDestructive` predicate flagging any `secret` change, routing those calls through `checkDestructiveRateLimit` + audit logging via the wrapper's existing duck-typed integration. URL and events changes stay on the regular path.
15
+
16
+ ### Changed
17
+
18
+ - **`ls_update_webhook` rejects empty PATCH locally.** Calling with only `webhookId` and no fields to change previously sent an empty `attributes: {}` PATCH that the API would 422 with a less clear message. The handler now throws `"ls_update_webhook requires at least one of: url, events, secret"` before the round-trip.
19
+ - **`ls_update_webhook` description warns about secret rotation.** The tool-level description now calls out that setting `secret` is destructive (rate-limited and audited) so LLM callers see the consequence before invoking.
20
+
21
+ ### Tests
22
+
23
+ - **Regression coverage for the `events.min(1)` fix** (`f7374bd`): schema-rejection tests on both `ls_create_webhook` and `ls_update_webhook` lock in the empty-array rejection.
24
+ - **URL validation coverage**: non-URL string rejection on both tools, four non-http(s) schemes (`mailto:`, `file:`, `javascript:`, `ftp:`) rejected on both tools, http and https explicit acceptance.
25
+ - **`ls_update_webhook` predicate tests** mirror the `ls_update_license_key` shape: secret-only as destructive, url-only and events-only as non-destructive, empty input as non-destructive.
26
+ - **`ls_update_webhook` URL-encoding** test added in parallel to the existing `ls_delete_webhook` coverage.
27
+ - **Handler-side empty-PATCH rejection** test asserts the new error message.
28
+
29
+ ## [0.10.9] -- 2026-05-22
30
+
31
+ ### Changed
32
+
33
+ - **Webhook `events` array now requires at least one entry.** `ls_create_webhook` and `ls_update_webhook` previously let an empty array pass local Zod validation, so a no-op webhook configuration only failed at the LemonSqueezy API as a 422. The new `.min(1)` makes the rejection local with a clearer message; on update, the field stays optional but, when supplied, must be non-empty.
34
+
5
35
  ## [0.10.8] -- 2026-05-19
6
36
 
7
37
  ### Security
@@ -359,7 +389,9 @@ Hardening pass for unattended automation against live billing flows.
359
389
 
360
390
  Initial release. 59 tools covering all 17 LemonSqueezy API resources.
361
391
 
362
- [Unreleased]: https://github.com/YawLabs/lemonsqueezy-mcp/compare/v0.10.8...HEAD
392
+ [Unreleased]: https://github.com/YawLabs/lemonsqueezy-mcp/compare/v0.10.9...HEAD
393
+ [0.10.10]: https://github.com/YawLabs/lemonsqueezy-mcp/compare/v0.10.9...v0.10.10
394
+ [0.10.9]: https://github.com/YawLabs/lemonsqueezy-mcp/compare/v0.10.8...v0.10.9
363
395
  [0.10.8]: https://github.com/YawLabs/lemonsqueezy-mcp/compare/v0.10.7...v0.10.8
364
396
  [0.10.7]: https://github.com/YawLabs/lemonsqueezy-mcp/compare/v0.10.6...v0.10.7
365
397
  [0.10.6]: https://github.com/YawLabs/lemonsqueezy-mcp/compare/v0.10.5...v0.10.6
package/dist/index.js CHANGED
@@ -32541,6 +32541,7 @@ var variantTools = [
32541
32541
  ];
32542
32542
 
32543
32543
  // src/tools/webhooks.ts
32544
+ var httpsUrlSchema = external_exports3.string().url().max(1e4).refine((u) => /^https?:\/\//i.test(u), { message: "url must start with http:// or https://" });
32544
32545
  var webhookTools = [
32545
32546
  {
32546
32547
  name: "ls_get_webhook",
@@ -32591,8 +32592,8 @@ var webhookTools = [
32591
32592
  },
32592
32593
  inputSchema: external_exports3.object({
32593
32594
  storeId: lsIdSchema.describe("The store ID"),
32594
- url: external_exports3.string().max(1e4).describe("The URL to send webhook events to"),
32595
- events: external_exports3.array(external_exports3.string()).describe(
32595
+ url: httpsUrlSchema.describe("The URL to send webhook events to (must be a valid http/https URL)"),
32596
+ events: external_exports3.array(external_exports3.string()).min(1).describe(
32596
32597
  "Event types to subscribe to (e.g. ['order_created', 'subscription_created', 'subscription_updated', 'subscription_cancelled', 'subscription_payment_success', 'subscription_payment_failed', 'license_key_created'])"
32597
32598
  ),
32598
32599
  secret: external_exports3.string().min(6).max(40).describe("A signing secret for verifying webhook payloads")
@@ -32616,7 +32617,7 @@ var webhookTools = [
32616
32617
  {
32617
32618
  name: "ls_update_webhook",
32618
32619
  authorityClass: "webhook",
32619
- description: "Update an existing webhook's URL, events, or secret.",
32620
+ description: "Update an existing webhook's URL, events, or secret. Setting `secret` rotates the signing secret and breaks signature verification on the receiver until they update their copy -- this is treated as destructive (rate-limited and audited).",
32620
32621
  annotations: {
32621
32622
  title: "Update webhook",
32622
32623
  readOnlyHint: false,
@@ -32624,10 +32625,15 @@ var webhookTools = [
32624
32625
  idempotentHint: true,
32625
32626
  openWorldHint: true
32626
32627
  },
32628
+ // Rotating the signing secret silently breaks signature verification on
32629
+ // every receiver until they redeploy with the new value. URL and events
32630
+ // changes are observable on the next event but don't break anything in
32631
+ // flight, so they stay on the regular path.
32632
+ isDestructive: (input) => input.secret !== void 0,
32627
32633
  inputSchema: external_exports3.object({
32628
32634
  webhookId: lsIdSchema.describe("The webhook ID to update"),
32629
- url: external_exports3.string().max(1e4).optional().describe("New URL to send webhook events to"),
32630
- events: external_exports3.array(external_exports3.string()).optional().describe("Updated list of event types to subscribe to"),
32635
+ url: httpsUrlSchema.optional().describe("New URL to send webhook events to (must be a valid http/https URL)"),
32636
+ events: external_exports3.array(external_exports3.string()).min(1).optional().describe("Updated list of event types to subscribe to"),
32631
32637
  secret: external_exports3.string().min(6).max(40).optional().describe("New signing secret")
32632
32638
  }),
32633
32639
  handler: async (input) => {
@@ -32635,6 +32641,9 @@ var webhookTools = [
32635
32641
  if (input.url !== void 0) attributes.url = input.url;
32636
32642
  if (input.events !== void 0) attributes.events = input.events;
32637
32643
  if (input.secret !== void 0) attributes.secret = input.secret;
32644
+ if (Object.keys(attributes).length === 0) {
32645
+ throw new Error("ls_update_webhook requires at least one of: url, events, secret");
32646
+ }
32638
32647
  return apiPatch(`/webhooks/${encodePath(input.webhookId)}`, {
32639
32648
  data: {
32640
32649
  type: "webhooks",
@@ -32831,7 +32840,7 @@ function readAuditLogResource(uri) {
32831
32840
  }
32832
32841
 
32833
32842
  // src/index.ts
32834
- var version2 = true ? "0.10.8" : (await null).createRequire(import.meta.url)("../package.json").version;
32843
+ var version2 = true ? "0.10.10" : (await null).createRequire(import.meta.url)("../package.json").version;
32835
32844
  var subcommand = process.argv[2];
32836
32845
  if (subcommand === "version" || subcommand === "--version") {
32837
32846
  console.log(version2);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yawlabs/lemonsqueezy-mcp",
3
- "version": "0.10.8",
3
+ "version": "0.10.10",
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",