@yawlabs/lemonsqueezy-mcp 0.4.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +22 -5
  2. package/dist/index.js +82 -36
  3. package/package.json +54 -55
package/README.md CHANGED
@@ -54,7 +54,7 @@ Add to `claude_desktop_config.json`:
54
54
  }
55
55
  ```
56
56
 
57
- ## Tools (59)
57
+ ## Tools (61)
58
58
 
59
59
  ### Users
60
60
  - `ls_get_user` — Get the authenticated user
@@ -157,7 +157,7 @@ Add to `claude_desktop_config.json`:
157
157
 
158
158
  ## Features
159
159
 
160
- - **Full API coverage** — All 17 LemonSqueezy API resources with 59 tools
160
+ - **Full API coverage** — All 17 LemonSqueezy API resources with 61 tools
161
161
  - **JSON:API support** — Filtering, pagination, and relationship inclusion on all list/get operations
162
162
  - **Zero runtime dependencies** — Single bundled file for instant `npx` startup
163
163
  - **License API** — Activate, validate, and deactivate license keys without an API key
@@ -174,9 +174,9 @@ All configuration is via environment variables. Only `LEMONSQUEEZY_API_KEY` (or
174
174
  | --- | --- |
175
175
  | `LEMONSQUEEZY_API_KEY` | LemonSqueezy API token. |
176
176
  | `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. |
177
- | `LEMONSQUEEZY_ALLOWED_STORE_IDS` | Comma-separated allowlist of store IDs. When set, tools that receive a `storeId` input reject calls to any other store. Note: operations that don't take an explicit `storeId` (e.g. `ls_refund_order`) are not gated by this — pair with `LEMONSQUEEZY_MAX_REFUND_AMOUNT_CENTS`. |
178
- | `LEMONSQUEEZY_MAX_REFUND_AMOUNT_CENTS` | Rejects `ls_refund_order` calls above this amount. |
179
- | `LEMONSQUEEZY_DESTRUCTIVE_RATE_LIMIT` | Max destructive tool calls per 60-second rolling window. In-process limit — per MCP server instance, not global. |
177
+ | `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_list_stores`) are not gated by this — pair with `LEMONSQUEEZY_MAX_REFUND_AMOUNT_CENTS` and `LEMONSQUEEZY_DESTRUCTIVE_RATE_LIMIT`. |
178
+ | `LEMONSQUEEZY_MAX_REFUND_AMOUNT_CENTS` | Rejects `ls_refund_order` and `ls_refund_subscription_invoice` calls above this amount. |
179
+ | `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`. |
180
180
  | `LEMONSQUEEZY_LOG=json` | Emit one JSON log line to stderr per tool and HTTP call. Destructive calls are tagged `audit: true` and include their inputs. |
181
181
 
182
182
  ### Logging format
@@ -214,6 +214,23 @@ npm test # full unit + handler suite
214
214
  npm run test:integration # requires LEMONSQUEEZY_TEST_API_KEY + LEMONSQUEEZY_TEST_STORE_ID
215
215
  ```
216
216
 
217
+ ## Releasing
218
+
219
+ Releases are cut locally — there is no CI pipeline. From a clean checkout of `main`:
220
+
221
+ ```bash
222
+ ./release.sh 0.6.0
223
+ ```
224
+
225
+ The script lints, tests, builds, bumps the version, commits and tags, pushes to `origin`, publishes to npm, and creates a GitHub release. Each step is idempotent — re-running with the same version after a partial failure resumes from where it stopped.
226
+
227
+ One-time setup on the release machine:
228
+
229
+ ```bash
230
+ npm login --auth-type=web # publisher of @yawlabs/lemonsqueezy-mcp
231
+ gh auth login # GitHub CLI for the release-creation step
232
+ ```
233
+
217
234
  ## License
218
235
 
219
236
  MIT
package/dist/index.js CHANGED
@@ -21073,6 +21073,24 @@ function checkDestructiveRateLimit(now = Date.now()) {
21073
21073
  }
21074
21074
  destructiveTimestamps.push(now);
21075
21075
  }
21076
+ function isStoreAllowlistActive() {
21077
+ return loadOptions().allowedStoreIds !== null;
21078
+ }
21079
+ function checkStoreScopedToolInput(toolAcceptsStoreId, input) {
21080
+ if (!toolAcceptsStoreId) return;
21081
+ const raw = input.storeId;
21082
+ if (raw !== void 0 && raw !== null && raw !== "") {
21083
+ checkStoreAllowed(String(raw));
21084
+ return;
21085
+ }
21086
+ if (isStoreAllowlistActive()) {
21087
+ throw new GuardrailError("storeId is required when LEMONSQUEEZY_ALLOWED_STORE_IDS is set");
21088
+ }
21089
+ }
21090
+ function isDestructiveCall(tool, input) {
21091
+ if (typeof tool.isDestructive === "function") return tool.isDestructive(input);
21092
+ return tool.annotations?.destructiveHint === true;
21093
+ }
21076
21094
 
21077
21095
  // src/logger.ts
21078
21096
  function isEnabled() {
@@ -21083,6 +21101,25 @@ function logEvent(entry) {
21083
21101
  try {
21084
21102
  const line = JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), ...entry });
21085
21103
  process.stderr.write(`${line}
21104
+ `);
21105
+ return;
21106
+ } catch {
21107
+ }
21108
+ try {
21109
+ const fallback = JSON.stringify({
21110
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
21111
+ event: entry.event,
21112
+ tool: entry.tool,
21113
+ method: entry.method,
21114
+ path: entry.path,
21115
+ status: entry.status,
21116
+ latency_ms: entry.latency_ms,
21117
+ request_id: entry.request_id,
21118
+ audit: entry.audit,
21119
+ error: entry.error,
21120
+ log_error: "inputs_not_serializable"
21121
+ });
21122
+ process.stderr.write(`${fallback}
21086
21123
  `);
21087
21124
  } catch {
21088
21125
  }
@@ -21223,6 +21260,9 @@ async function loadApiKey() {
21223
21260
 
21224
21261
  // src/api.ts
21225
21262
  var BASE_URL = "https://api.lemonsqueezy.com/v1";
21263
+ function encodePath(segment) {
21264
+ return encodeURIComponent(String(segment));
21265
+ }
21226
21266
  function buildQuery(params) {
21227
21267
  if (!params) return "";
21228
21268
  const parts = [];
@@ -21421,7 +21461,7 @@ async function licenseRequest(path, body) {
21421
21461
  function getHandler(endpoint, idField) {
21422
21462
  return async (input) => {
21423
21463
  const query = buildQuery({ include: input.include?.split(",") });
21424
- return apiGet(`${endpoint}/${input[idField]}${query}`);
21464
+ return apiGet(`${endpoint}/${encodePath(input[idField])}${query}`);
21425
21465
  };
21426
21466
  }
21427
21467
  function listHandler(endpoint, filterMap = {}) {
@@ -21548,7 +21588,7 @@ var checkoutTools = [
21548
21588
  billingAddressZip: external_exports.string().max(1e4).optional().describe("Prefill billing ZIP/postal code"),
21549
21589
  taxNumber: external_exports.string().max(1e4).optional().describe("Prefill tax/VAT number"),
21550
21590
  discountCode: external_exports.string().max(1e4).optional().describe("Pre-apply a discount code"),
21551
- customData: external_exports.record(external_exports.unknown()).optional().describe("Custom data object to attach to the order"),
21591
+ customData: external_exports.record(external_exports.string(), external_exports.unknown()).optional().describe("Custom data object to attach to the order"),
21552
21592
  expiresAt: external_exports.string().max(1e4).optional().describe("Checkout expiry date (ISO 8601 format)")
21553
21593
  }),
21554
21594
  handler: async (input) => {
@@ -21559,13 +21599,11 @@ var checkoutTools = [
21559
21599
  const checkoutData = {};
21560
21600
  if (input.email !== void 0) checkoutData.email = input.email;
21561
21601
  if (input.name !== void 0) checkoutData.name = input.name;
21562
- if (input.billingAddressCountry !== void 0)
21563
- checkoutData.billing_address = { country: input.billingAddressCountry };
21564
- if (input.billingAddressZip !== void 0) {
21565
- checkoutData.billing_address = {
21566
- ...checkoutData.billing_address || {},
21567
- zip: input.billingAddressZip
21568
- };
21602
+ if (input.billingAddressCountry !== void 0 || input.billingAddressZip !== void 0) {
21603
+ const billingAddress = {};
21604
+ if (input.billingAddressCountry !== void 0) billingAddress.country = input.billingAddressCountry;
21605
+ if (input.billingAddressZip !== void 0) billingAddress.zip = input.billingAddressZip;
21606
+ checkoutData.billing_address = billingAddress;
21569
21607
  }
21570
21608
  if (input.taxNumber !== void 0) checkoutData.tax_number = input.taxNumber;
21571
21609
  if (input.discountCode !== void 0) checkoutData.discount_code = input.discountCode;
@@ -21617,7 +21655,7 @@ var customerTools = [
21617
21655
  },
21618
21656
  inputSchema: external_exports.object({
21619
21657
  storeId: external_exports.string().max(1e4).optional().describe("Filter by store ID"),
21620
- email: external_exports.string().max(1e4).optional().describe("Filter by customer email"),
21658
+ email: external_exports.string().email().max(320).optional().describe("Filter by customer email"),
21621
21659
  include: external_exports.string().max(1e4).optional().describe("Comma-separated related resources to include (e.g. 'store,orders,subscriptions,license-keys')"),
21622
21660
  pageNumber: external_exports.number().int().min(1).optional().describe("Page number (1-indexed)"),
21623
21661
  pageSize: external_exports.number().int().min(1).max(100).optional().describe("Results per page (1-100)")
@@ -21637,7 +21675,7 @@ var customerTools = [
21637
21675
  inputSchema: external_exports.object({
21638
21676
  storeId: external_exports.string().max(1e4).describe("The store ID to create the customer in"),
21639
21677
  name: external_exports.string().max(1e4).describe("Customer's full name"),
21640
- email: external_exports.string().max(1e4).describe("Customer's email address"),
21678
+ email: external_exports.string().email().max(320).describe("Customer's email address"),
21641
21679
  city: external_exports.string().max(1e4).optional().describe("Customer's city"),
21642
21680
  region: external_exports.string().max(1e4).optional().describe("Customer's region/state"),
21643
21681
  country: external_exports.string().max(1e4).optional().describe("Customer's country (ISO 3166-1 alpha-2 code, e.g. 'US')")
@@ -21674,7 +21712,7 @@ var customerTools = [
21674
21712
  inputSchema: external_exports.object({
21675
21713
  customerId: external_exports.string().max(1e4).describe("The customer ID to update"),
21676
21714
  name: external_exports.string().max(1e4).optional().describe("New name"),
21677
- email: external_exports.string().max(1e4).optional().describe("New email"),
21715
+ email: external_exports.string().email().max(320).optional().describe("New email"),
21678
21716
  city: external_exports.string().max(1e4).optional().describe("New city"),
21679
21717
  region: external_exports.string().max(1e4).optional().describe("New region/state"),
21680
21718
  country: external_exports.string().max(1e4).optional().describe("New country (ISO 3166-1 alpha-2 code)"),
@@ -21688,7 +21726,7 @@ var customerTools = [
21688
21726
  if (input.region !== void 0) attributes.region = input.region;
21689
21727
  if (input.country !== void 0) attributes.country = input.country;
21690
21728
  if (input.status !== void 0) attributes.status = input.status;
21691
- return apiPatch(`/customers/${input.customerId}`, {
21729
+ return apiPatch(`/customers/${encodePath(input.customerId)}`, {
21692
21730
  data: {
21693
21731
  type: "customers",
21694
21732
  id: input.customerId,
@@ -21711,7 +21749,7 @@ var customerTools = [
21711
21749
  customerId: external_exports.string().max(1e4).describe("The customer ID to archive")
21712
21750
  }),
21713
21751
  handler: async (input) => {
21714
- return apiPatch(`/customers/${input.customerId}`, {
21752
+ return apiPatch(`/customers/${encodePath(input.customerId)}`, {
21715
21753
  data: {
21716
21754
  type: "customers",
21717
21755
  id: input.customerId,
@@ -21869,7 +21907,7 @@ var discountTools = [
21869
21907
  discountId: external_exports.string().max(1e4).describe("The discount ID to delete")
21870
21908
  }),
21871
21909
  handler: async (input) => {
21872
- return apiDelete(`/discounts/${input.discountId}`);
21910
+ return apiDelete(`/discounts/${encodePath(input.discountId)}`);
21873
21911
  }
21874
21912
  }
21875
21913
  ];
@@ -22000,7 +22038,7 @@ var licenseKeyTools = [
22000
22038
  },
22001
22039
  {
22002
22040
  name: "ls_update_license_key",
22003
- description: "Update a license key's activation limit, expiry date, or disabled status.",
22041
+ description: "Update a license key's activation limit, expiry date, or disabled status. Setting `disabled: true` revokes customer access and is treated as destructive (rate-limited and audited).",
22004
22042
  annotations: {
22005
22043
  title: "Update license key",
22006
22044
  readOnlyHint: false,
@@ -22008,6 +22046,11 @@ var licenseKeyTools = [
22008
22046
  idempotentHint: true,
22009
22047
  openWorldHint: true
22010
22048
  },
22049
+ // Disabling a license key revokes a customer's access. Tag the call as
22050
+ // destructive only when `disabled: true` so the rate limiter and audit log
22051
+ // engage on revocation, while benign edits (expiry, activation limit) stay
22052
+ // on the regular path.
22053
+ isDestructive: (input) => input.disabled === true,
22011
22054
  inputSchema: external_exports.object({
22012
22055
  licenseKeyId: external_exports.string().max(1e4).describe("The license key ID to update"),
22013
22056
  activationLimit: external_exports.number().int().min(0).optional().describe("Maximum number of activations allowed (0 = unlimited)"),
@@ -22019,7 +22062,7 @@ var licenseKeyTools = [
22019
22062
  if (input.activationLimit !== void 0) attributes.activation_limit = input.activationLimit;
22020
22063
  if (input.disabled !== void 0) attributes.disabled = input.disabled;
22021
22064
  if (input.expiresAt !== void 0) attributes.expires_at = input.expiresAt;
22022
- return apiPatch(`/license-keys/${input.licenseKeyId}`, {
22065
+ return apiPatch(`/license-keys/${encodePath(input.licenseKeyId)}`, {
22023
22066
  data: {
22024
22067
  type: "license-keys",
22025
22068
  id: input.licenseKeyId,
@@ -22168,7 +22211,7 @@ var orderTools = [
22168
22211
  },
22169
22212
  inputSchema: external_exports.object({
22170
22213
  storeId: external_exports.string().max(1e4).optional().describe("Filter by store ID"),
22171
- userEmail: external_exports.string().max(1e4).optional().describe("Filter by user email"),
22214
+ userEmail: external_exports.string().email().max(320).optional().describe("Filter by user email"),
22172
22215
  include: external_exports.string().max(1e4).optional().describe(
22173
22216
  "Comma-separated related resources to include (e.g. 'store,customer,order-items,subscriptions,license-keys,discount-redemptions')"
22174
22217
  ),
@@ -22209,7 +22252,7 @@ var orderTools = [
22209
22252
  if (input.notes !== void 0) params.set("notes", input.notes);
22210
22253
  if (input.locale !== void 0) params.set("locale", input.locale);
22211
22254
  const qs = params.toString();
22212
- return apiPost(`/orders/${input.orderId}/generate-invoice${qs ? `?${qs}` : ""}`);
22255
+ return apiPost(`/orders/${encodePath(input.orderId)}/generate-invoice${qs ? `?${qs}` : ""}`);
22213
22256
  }
22214
22257
  },
22215
22258
  {
@@ -22228,7 +22271,7 @@ var orderTools = [
22228
22271
  }),
22229
22272
  handler: async (input) => {
22230
22273
  checkRefundAmount(input.amount);
22231
- return apiPost(`/orders/${input.orderId}/refund`, {
22274
+ return apiPost(`/orders/${encodePath(input.orderId)}/refund`, {
22232
22275
  data: { type: "orders", id: input.orderId, attributes: { amount: input.amount } }
22233
22276
  });
22234
22277
  }
@@ -22428,7 +22471,9 @@ var subscriptionInvoiceTools = [
22428
22471
  if (input.notes !== void 0) params.set("notes", input.notes);
22429
22472
  if (input.locale !== void 0) params.set("locale", input.locale);
22430
22473
  const qs = params.toString();
22431
- return apiPost(`/subscription-invoices/${input.subscriptionInvoiceId}/generate-invoice${qs ? `?${qs}` : ""}`);
22474
+ return apiPost(
22475
+ `/subscription-invoices/${encodePath(input.subscriptionInvoiceId)}/generate-invoice${qs ? `?${qs}` : ""}`
22476
+ );
22432
22477
  }
22433
22478
  },
22434
22479
  {
@@ -22446,7 +22491,8 @@ var subscriptionInvoiceTools = [
22446
22491
  amount: external_exports.number().int().min(1).describe("Refund amount in cents (e.g. 1000 = $10.00)")
22447
22492
  }),
22448
22493
  handler: async (input) => {
22449
- return apiPost(`/subscription-invoices/${input.subscriptionInvoiceId}/refund`, {
22494
+ checkRefundAmount(input.amount);
22495
+ return apiPost(`/subscription-invoices/${encodePath(input.subscriptionInvoiceId)}/refund`, {
22450
22496
  data: {
22451
22497
  type: "subscription-invoices",
22452
22498
  id: input.subscriptionInvoiceId,
@@ -22509,7 +22555,7 @@ var subscriptionItemTools = [
22509
22555
  quantity: external_exports.number().int().min(1).describe("New quantity for the subscription item")
22510
22556
  }),
22511
22557
  handler: async (input) => {
22512
- return apiPatch(`/subscription-items/${input.subscriptionItemId}`, {
22558
+ return apiPatch(`/subscription-items/${encodePath(input.subscriptionItemId)}`, {
22513
22559
  data: {
22514
22560
  type: "subscription-items",
22515
22561
  id: input.subscriptionItemId,
@@ -22532,7 +22578,7 @@ var subscriptionItemTools = [
22532
22578
  subscriptionItemId: external_exports.string().max(1e4).describe("The subscription item ID")
22533
22579
  }),
22534
22580
  handler: async (input) => {
22535
- return apiGet(`/subscription-items/${input.subscriptionItemId}/current-usage`);
22581
+ return apiGet(`/subscription-items/${encodePath(input.subscriptionItemId)}/current-usage`);
22536
22582
  }
22537
22583
  }
22538
22584
  ];
@@ -22573,7 +22619,7 @@ var subscriptionTools = [
22573
22619
  orderItemId: external_exports.string().max(1e4).optional().describe("Filter by order item ID"),
22574
22620
  productId: external_exports.string().max(1e4).optional().describe("Filter by product ID"),
22575
22621
  variantId: external_exports.string().max(1e4).optional().describe("Filter by variant ID"),
22576
- userEmail: external_exports.string().max(1e4).optional().describe("Filter by user email"),
22622
+ userEmail: external_exports.string().email().max(320).optional().describe("Filter by user email"),
22577
22623
  status: external_exports.enum(["on_trial", "active", "paused", "past_due", "unpaid", "cancelled", "expired"]).optional().describe("Filter by subscription status"),
22578
22624
  include: external_exports.string().max(1e4).optional().describe(
22579
22625
  "Comma-separated related resources to include (e.g. 'store,customer,order,order-item,product,variant')"
@@ -22603,7 +22649,7 @@ var subscriptionTools = [
22603
22649
  },
22604
22650
  inputSchema: external_exports.object({
22605
22651
  subscriptionId: external_exports.string().max(1e4).describe("The subscription ID to update"),
22606
- variantId: external_exports.string().max(1e4).optional().describe("New variant ID for plan switching"),
22652
+ variantId: external_exports.string().max(1e4).regex(/^[1-9]\d*$/, "variantId must be a positive integer string (e.g. '12345')").optional().describe("New variant ID for plan switching"),
22607
22653
  pause: external_exports.enum(["void", "free", "resume"]).optional().describe("Pause mode: 'void' (pause, skip billing), 'free' (pause, keep access free), or 'resume' to unpause"),
22608
22654
  cancelled: external_exports.literal(false).optional().describe(
22609
22655
  "Set to false to un-cancel a subscription before it expires. To cancel, use ls_cancel_subscription instead."
@@ -22611,7 +22657,7 @@ var subscriptionTools = [
22611
22657
  billingAnchor: external_exports.number().int().min(1).max(28).optional().describe("Day of month (1-28) to anchor billing to"),
22612
22658
  invoiceImmediately: external_exports.boolean().optional().describe("If true, invoice immediately when updating (default false for prorated changes)"),
22613
22659
  disableProrations: external_exports.boolean().optional().describe("If true, disable prorations when changing plans"),
22614
- trialEndsAt: external_exports.string().max(1e4).optional().describe("Set trial end date (ISO 8601 format). Set to null to end trial immediately.")
22660
+ trialEndsAt: external_exports.string().max(1e4).nullable().optional().describe("Set trial end date (ISO 8601 format). Set to null to end trial immediately.")
22615
22661
  }),
22616
22662
  handler: async (input) => {
22617
22663
  const attributes = {};
@@ -22622,7 +22668,7 @@ var subscriptionTools = [
22622
22668
  if (input.invoiceImmediately !== void 0) attributes.invoice_immediately = input.invoiceImmediately;
22623
22669
  if (input.disableProrations !== void 0) attributes.disable_prorations = input.disableProrations;
22624
22670
  if (input.trialEndsAt !== void 0) attributes.trial_ends_at = input.trialEndsAt;
22625
- return apiPatch(`/subscriptions/${input.subscriptionId}`, {
22671
+ return apiPatch(`/subscriptions/${encodePath(input.subscriptionId)}`, {
22626
22672
  data: {
22627
22673
  type: "subscriptions",
22628
22674
  id: input.subscriptionId,
@@ -22645,7 +22691,7 @@ var subscriptionTools = [
22645
22691
  subscriptionId: external_exports.string().max(1e4).describe("The subscription ID to cancel")
22646
22692
  }),
22647
22693
  handler: async (input) => {
22648
- return apiDelete(`/subscriptions/${input.subscriptionId}`);
22694
+ return apiDelete(`/subscriptions/${encodePath(input.subscriptionId)}`);
22649
22695
  }
22650
22696
  }
22651
22697
  ];
@@ -22862,14 +22908,14 @@ var webhookTools = [
22862
22908
  webhookId: external_exports.string().max(1e4).describe("The webhook ID to update"),
22863
22909
  url: external_exports.string().max(1e4).optional().describe("New URL to send webhook events to"),
22864
22910
  events: external_exports.array(external_exports.string()).optional().describe("Updated list of event types to subscribe to"),
22865
- secret: external_exports.string().max(1e4).optional().describe("New signing secret")
22911
+ secret: external_exports.string().min(6).max(40).optional().describe("New signing secret")
22866
22912
  }),
22867
22913
  handler: async (input) => {
22868
22914
  const attributes = {};
22869
22915
  if (input.url !== void 0) attributes.url = input.url;
22870
22916
  if (input.events !== void 0) attributes.events = input.events;
22871
22917
  if (input.secret !== void 0) attributes.secret = input.secret;
22872
- return apiPatch(`/webhooks/${input.webhookId}`, {
22918
+ return apiPatch(`/webhooks/${encodePath(input.webhookId)}`, {
22873
22919
  data: {
22874
22920
  type: "webhooks",
22875
22921
  id: input.webhookId,
@@ -22892,13 +22938,13 @@ var webhookTools = [
22892
22938
  webhookId: external_exports.string().max(1e4).describe("The webhook ID to delete")
22893
22939
  }),
22894
22940
  handler: async (input) => {
22895
- return apiDelete(`/webhooks/${input.webhookId}`);
22941
+ return apiDelete(`/webhooks/${encodePath(input.webhookId)}`);
22896
22942
  }
22897
22943
  }
22898
22944
  ];
22899
22945
 
22900
22946
  // src/index.ts
22901
- var version2 = true ? "0.4.1" : (await null).createRequire(import.meta.url)("../package.json").version;
22947
+ var version2 = true ? "0.6.0" : (await null).createRequire(import.meta.url)("../package.json").version;
22902
22948
  var subcommand = process.argv[2];
22903
22949
  if (subcommand === "version" || subcommand === "--version") {
22904
22950
  console.log(version2);
@@ -22932,18 +22978,18 @@ var server = new McpServer({
22932
22978
  version: version2
22933
22979
  });
22934
22980
  for (const tool of allTools) {
22935
- const isDestructive = tool.annotations?.destructiveHint === true;
22981
+ const toolAcceptsStoreId = "storeId" in tool.inputSchema.shape;
22936
22982
  server.tool(
22937
22983
  tool.name,
22938
22984
  tool.description,
22939
22985
  tool.inputSchema.shape,
22940
22986
  tool.annotations,
22941
22987
  async (input) => {
22988
+ const isDestructive = isDestructiveCall(tool, input);
22942
22989
  const start = Date.now();
22943
22990
  try {
22944
22991
  if (isDestructive) checkDestructiveRateLimit();
22945
- const storeId = input.storeId;
22946
- if (typeof storeId === "string") checkStoreAllowed(storeId);
22992
+ checkStoreScopedToolInput(toolAcceptsStoreId, input);
22947
22993
  const result = await tool.handler(input);
22948
22994
  const response = result;
22949
22995
  const latency_ms = Date.now() - start;
package/package.json CHANGED
@@ -1,55 +1,54 @@
1
- {
2
- "name": "@yawlabs/lemonsqueezy-mcp",
3
- "version": "0.4.1",
4
- "description": "LemonSqueezy MCP server for managing your store from AI assistants",
5
- "license": "MIT",
6
- "author": "YawLabs <contact@yaw.sh>",
7
- "repository": {
8
- "type": "git",
9
- "url": "git+https://github.com/YawLabs/lemonsqueezy-mcp.git"
10
- },
11
- "keywords": [
12
- "lemonsqueezy",
13
- "lemon-squeezy",
14
- "mcp",
15
- "model-context-protocol",
16
- "ai",
17
- "payments",
18
- "subscriptions",
19
- "licensing"
20
- ],
21
- "type": "module",
22
- "main": "dist/index.js",
23
- "bin": {
24
- "lemonsqueezy-mcp": "dist/index.js"
25
- },
26
- "files": [
27
- "dist/index.js"
28
- ],
29
- "scripts": {
30
- "build": "tsc && node build.mjs",
31
- "dev": "tsc --watch",
32
- "start": "node dist/index.js",
33
- "test": "npm run build && node --test dist/**/*.test.js",
34
- "test:ci": "npm run test",
35
- "test:integration": "npm run build && node --test dist/integration/*.test.js",
36
- "lint": "biome check src/",
37
- "lint:fix": "biome check --write src/",
38
- "prepublishOnly": "npm run build && node --test dist/**/*.test.js"
39
- },
40
- "dependencies": {},
41
- "overrides": {
42
- "hono": "^4.12.14"
43
- },
44
- "devDependencies": {
45
- "@biomejs/biome": "^1.9.4",
46
- "@modelcontextprotocol/sdk": "^1.29.0",
47
- "@types/node": "^22.15.2",
48
- "esbuild": "^0.28.0",
49
- "typescript": "^5.8.3",
50
- "zod": "^3.24.4"
51
- },
52
- "engines": {
53
- "node": ">=18"
54
- }
55
- }
1
+ {
2
+ "name": "@yawlabs/lemonsqueezy-mcp",
3
+ "version": "0.6.0",
4
+ "description": "LemonSqueezy MCP server for managing your store from AI assistants",
5
+ "license": "MIT",
6
+ "author": "YawLabs <contact@yaw.sh>",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/YawLabs/lemonsqueezy-mcp.git"
10
+ },
11
+ "keywords": [
12
+ "lemonsqueezy",
13
+ "lemon-squeezy",
14
+ "mcp",
15
+ "model-context-protocol",
16
+ "ai",
17
+ "payments",
18
+ "subscriptions",
19
+ "licensing"
20
+ ],
21
+ "type": "module",
22
+ "main": "dist/index.js",
23
+ "bin": {
24
+ "lemonsqueezy-mcp": "dist/index.js"
25
+ },
26
+ "files": [
27
+ "dist/index.js"
28
+ ],
29
+ "scripts": {
30
+ "build": "tsc && node build.mjs",
31
+ "dev": "tsc --watch",
32
+ "start": "node dist/index.js",
33
+ "test": "npm run build && node --test dist/**/*.test.js",
34
+ "test:integration": "npm run build && node --test dist/integration/*.test.js",
35
+ "lint": "biome check src/",
36
+ "lint:fix": "biome check --write src/",
37
+ "prepublishOnly": "npm run build && node --test dist/**/*.test.js"
38
+ },
39
+ "dependencies": {},
40
+ "overrides": {
41
+ "hono": "^4.12.14"
42
+ },
43
+ "devDependencies": {
44
+ "@biomejs/biome": "^2.4.12",
45
+ "@modelcontextprotocol/sdk": "^1.29.0",
46
+ "@types/node": "^25.6.0",
47
+ "esbuild": "^0.28.0",
48
+ "typescript": "^6.0.3",
49
+ "zod": "^4.3.6"
50
+ },
51
+ "engines": {
52
+ "node": ">=18"
53
+ }
54
+ }