@yawlabs/lemonsqueezy-mcp
This MCP server connects an AI assistant to the LemonSqueezy API so it can inspect and manage a LemonSqueezy store and related billing data.
Read authenticated user and store details, and list stores.
Manage customers: get, list, create, update, and archive.
Browse products, variants, prices, and files, including effective unit-price annotations.
Handle orders: get/list, generate PDF invoices, and issue refunds.
Manage subscriptions, subscription items, invoices, and cancellations/updates.
Report and inspect metered usage records.
Manage discounts and discount redemptions.
Manage license keys and activations, and use the License API to activate, validate, or deactivate keys.
Create and manage checkouts, webhooks, and affiliate records.
Optionally bridge to a webhook sink to list events, mark events processed, and get sink stats.
Supports pagination, filters, relationship includes, guardrails, rate limits, destructive-call auditing, and retry/backoff behavior.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@@yawlabs/lemonsqueezy-mcp How many active subscriptions do I have?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
@yawlabs/lemonsqueezy-mcp
One click adds this to your local Yaw MCP config so it's available in every Yaw Terminal session. Or install manually below.
MCP server for the LemonSqueezy API. Manage your store, products, customers, subscriptions, discounts, license keys, and more from any MCP-compatible AI assistant.
Quick start
npx -y @yawlabs/lemonsqueezy-mcp@latestRequires Node.js 22 or later (engines.node is >=22).
Or one-click install via Smithery:
npx -y @smithery/cli install @yawlabs/lemonsqueezy-mcp --client claudeSmithery prompts for your env vars (API key, optional guardrails) and writes the config into your client for you.
Related MCP server: mcp-server-woocommerce
What it looks like
Once configured, you can ask your AI assistant store-management questions in plain English and it routes them through the MCP tools:
You: How much did we make from the "Pro Annual" plan last month?
Claude: [calls ls_list_subscriptions, ls_get_variant, ls_list_subscription_invoices]
Pro Annual brought in $14,280 across 84 active subscriptions in April.
Three of those were upgrades from monthly; none churned.
You: Refund order #LS-1234 in full.
Claude: [calls ls_get_order to fetch the total, then ls_refund_order with amount = total]
Refunded $99.00 against order LS-1234. The customer's card will see the
credit in 5-10 business days.
You: Disable license key abc-123 for the customer who reported abuse.
Claude: [calls ls_list_license_keys to find the ID, then ls_update_license_key with disabled: true]
License key disabled. Their existing activations will fail validation
on the next check.Guardrails (refund cap, rate limit, store allowlist) catch the obvious mistakes before they reach LemonSqueezy. See Configuration for the env vars that turn them on.
Setup
Set your LemonSqueezy API key as an environment variable:
export LEMONSQUEEZY_API_KEY="your-api-key"Get your API key from your LemonSqueezy dashboard.
Docker
A multi-stage Dockerfile is included at the repo root. The runtime image is a single bundled file on node:22-alpine running as the non-root node user, with no port exposed (stdio transport).
docker build -t yawlabs/lemonsqueezy-mcp .
docker run --rm -i -e LEMONSQUEEZY_API_KEY="your-api-key" yawlabs/lemonsqueezy-mcpA matching Containerfile is provided for Podman users. It is generated from Dockerfile via npm run gen:containerfile. release.sh runs npm run check:containerfile in step 1, so a release fails if the two have drifted. This repo has no CI, so nothing checks a pull request for drift: run it yourself after editing Dockerfile.
Claude Code
Add to your MCP config:
{
"mcpServers": {
"lemonsqueezy": {
"command": "npx",
"args": ["-y", "@yawlabs/lemonsqueezy-mcp@latest"],
"env": {
"LEMONSQUEEZY_API_KEY": "your-api-key"
}
}
}
}Claude Desktop
Add to claude_desktop_config.json:
{
"mcpServers": {
"lemonsqueezy": {
"command": "npx",
"args": ["-y", "@yawlabs/lemonsqueezy-mcp@latest"],
"env": {
"LEMONSQUEEZY_API_KEY": "your-api-key"
}
}
}
}Yaw MCP
The Add to Yaw MCP button at the top adds the server without a key. Store the key in Yaw MCP's encrypted vault rather than as a plain env value:
yaw-mcp set lemonsqueezy env.LEMONSQUEEZY_API_KEY --secretIt prompts for the value without echoing it (or reads it from piped stdin) and writes only a ${secret:...} reference into ~/.yaw-mcp/bundles.json. The same works for LEMONSQUEEZY_TEST_API_KEY and LEMONSQUEEZY_SINK_ADMIN_TOKEN.
Yaw MCP also treats LEMONSQUEEZY_API_KEY_COMMAND as a credential, because its name contains KEY: yaw-mcp doctor flags a literal value as plaintext:, and the value is masked in captured stderr. That is expected. The variable holds a command line, not a key, so the flag is harmless, and you can store the command as a secret the same way to silence it.
Tools (64)
Users
ls_get_user— Get the authenticated user
Stores
ls_get_store— Get a store by IDls_list_stores— List all stores (not gated byLEMONSQUEEZY_ALLOWED_STORE_IDS: returns every store the API key can see)
Customers
ls_get_customer— Get a customer by IDls_list_customers— List customers (filter by store, email)ls_create_customer— Create a customerls_update_customer— Update a customerls_archive_customer— Archive a customer
Products
ls_get_product— Get a product by IDls_list_products— List products (filter by store)
Variants
ls_get_variant— Get a variant by IDls_list_variants— List variants (filter by product)
Prices
ls_get_price— Get a price by IDls_list_prices— List prices (filter by variant)
Both annotate every price record with effective_unit_price, the cents actually charged per unit, and effective_unit_price_note, which says where that number came from; unit_price_is_not_charged or unit_price_is_per_package is added where it applies. Read effective_unit_price rather than unit_price, which is vestigial on tiered (volume / graduated) pricing, or a variant's price. The subscription-item reads apply the same annotation to prices embedded via include=price.
Files
ls_get_file— Get a file by IDls_list_files— List files (filter by variant)
Orders
ls_get_order— Get an order by IDls_list_orders— List orders (filter by store, email)ls_generate_order_invoice— Generate a PDF invoicels_refund_order— Issue a refund
Order Items
ls_get_order_item— Get an order item by IDls_list_order_items— List order items (filter by order, product, variant)
Subscriptions
ls_get_subscription— Get a subscription by IDls_list_subscriptions— List subscriptions (filter by store, status, product, etc.)ls_update_subscription— Update (plan switch, pause, billing anchor, trial)ls_cancel_subscription— Cancel a subscription
Subscription Invoices
ls_get_subscription_invoice— Get a subscription invoice by IDls_list_subscription_invoices— List subscription invoices (filter by store, subscription, status)ls_generate_subscription_invoice— Generate a PDF invoicels_refund_subscription_invoice— Issue a refund
Subscription Items
ls_get_subscription_item— Get a subscription item by IDls_list_subscription_items— List subscription items (filter by subscription, price)ls_update_subscription_item— Update quantityls_get_subscription_item_usage— Get current billing period usage
Usage Records
ls_get_usage_record— Get a usage record by IDls_list_usage_records— List usage records (filter by subscription item)ls_create_usage_record— Report metered usage (increment or set)
Discounts
ls_get_discount— Get a discount by IDls_list_discounts— List discounts (filter by store)ls_create_discount— Create a discount codels_delete_discount— Delete a discount
Discount Redemptions
ls_get_discount_redemption— Get a discount redemption by IDls_list_discount_redemptions— List redemptions (filter by discount, order)
License Keys
ls_get_license_key— Get a license key by IDls_list_license_keys— List license keys (filter by store, order, product)ls_update_license_key— Update activation limit, expiry, or disabled status
License Key Instances
ls_get_license_key_instance— Get a license key activation by IDls_list_license_key_instances— List activations (filter by license key)
Checkouts
ls_get_checkout— Get a checkout by IDls_list_checkouts— List checkouts (filter by store, variant)ls_create_checkout— Create a checkout URL (custom pricing, prefill, discounts)
Webhooks
ls_get_webhook— Get a webhook by IDls_list_webhooks— List webhooks (filter by store)ls_create_webhook— Create a webhookls_update_webhook— Update a webhookls_delete_webhook— Delete a webhook
License API
ls_activate_license— Activate a license key (no API key required)ls_validate_license— Validate a license key (no API key required)ls_deactivate_license— Deactivate a license key instance (no API key required). Destructive on every call: rate-limited and audited, with the license key masked in the audit entry
Affiliates
ls_get_affiliate— Get an affiliate by ID (commission rate, status, earnings)ls_list_affiliates— List affiliates (filter by user email). Not gated byLEMONSQUEEZY_ALLOWED_STORE_IDS: returns affiliates from every store the API key can see
Webhook sink (optional)
Bridge to a separate @yawlabs/lemonsqueezy-webhook-sink process so the agent can reconcile against webhooks that actually fired. Tools are always registered; if LEMONSQUEEZY_SINK_URL / LEMONSQUEEZY_SINK_ADMIN_TOKEN are unset, calls return a clear "not configured" error.
ls_sink_events_list— List webhook events the sink has received (filter bysince/type/limit)ls_sink_event_mark_processed— Mark a sink event as processed by your consumer (idempotent)ls_sink_stats— Get total events, unprocessed count, and last-received timestamp
Features
API coverage — 58 tools across 20 LemonSqueezy API resources, 3 License API tools, and 3 bridge tools to an optional @yawlabs/lemonsqueezy-webhook-sink for webhook reconciliation (64 in total)
JSON:API support — Pagination on every
ls_list_*tool, filters on every one butls_list_stores, and relationship inclusion (include) on everyls_get_*/ls_list_*tool exceptls_get_userandls_get_subscription_item_usageZero runtime dependencies — Single bundled file for instant
npxstartupLicense API — Activate, validate, and deactivate license keys without an API key
MCP annotations — Every tool declares read-only, destructive, and idempotent hints
Retry with backoff — for LemonSqueezy API calls (the
ls_sink_*tools make a single attempt): a 429 is retried for every method, including POSTs such as refunds and License API calls, after waiting for the server'sRetry-After(1s if it is missing or unparseable). This relies on a 429 meaning the request was rejected before LemonSqueezy acted on it. IfRetry-Afteris over 30s, or the wait would pass the overall deadline, the 429 is returned instead. 5xx responses, timeouts and network errors are retried only for idempotent methods (GET, DELETE), with exponential backoff and jitter; a POST or PATCH that hits one is not retried. Up to 4 attempts, 30s per attempt, and no new attempt or wait is started after 90sGuardrails — 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)Audit log MCP Resource —
lemonsqueezy://audit-logexposes the last 1000 destructive-call entries asapplication/x-ndjsonfor clients without stderr accessStructured logging — opt-in JSON logs to stderr with selectable levels (
error,audit,all)
Configuration
All configuration is via environment variables. The management-API tools need one of LEMONSQUEEZY_API_KEY_COMMAND, LEMONSQUEEZY_TEST_API_KEY or LEMONSQUEEZY_API_KEY (checked in that order); the three License API tools and the ls_sink_* tools need none of them. Everything else is opt-in.
Variable | Purpose |
| LemonSqueezy API token. |
| Command whose stdout (trimmed, must be non-empty) is the API key. Takes precedence over |
| Optional test-mode key. When set and non-empty, it takes precedence over |
| Comma-separated allowlist of store IDs. When set: (1) any tool whose input includes a |
| Non-negative number. Rejects |
| Non-negative number. Max destructive tool calls per 60-second rolling window (a fraction behaves as the next whole number: |
| Comma-separated list of authority classes to refuse outright. Any tool whose class is listed returns a |
| Per-class rolling rate limits, comma-separated. Each entry is |
| Structured-log verbosity to stderr. Set to |
| Base URL of an optional @yawlabs/lemonsqueezy-webhook-sink instance (e.g. |
| Bearer token for the sink's admin endpoints. Must match the sink's |
Logging format
Each line: {ts, event, tool?, method?, path?, status, latency_ms, request_id?, error?, audit?, inputs?}. Stdout is reserved for the MCP protocol — never log there.
Error decoration
HTTP errors include the upstream X-Request-Id when present, so support tickets to LemonSqueezy can reference the exact call. When an error body is JSON with no message the server recognizes, it is surfaced whole, in both the error text and the log line, after the same redaction as audited inputs (see Resources). A non-JSON error body is passed through as-is.
Authority classes
The strongest access control LemonSqueezy itself exposes is the API key boundary. A LemonSqueezy API key authorizes its full account — every store, and every tool except the three License API tools (see below) and the three ls_sink_* bridge tools, which do not use it — 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.
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. That answer does not cover the License API tools (see below).
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.
Class | What it covers | Tools |
| Safe reads (list/get) that don't return customer PII as the primary payload. | Every |
| The dedicated customer-record tools. This class does not keep customer data out of reach; see the note below. |
|
| Non-money mutations: checkouts, discounts, invoice generation, marking sink events processed. |
|
| Money movement. Irreversible at the payment layer. |
|
| Subscription state and metered billing: changes that affect recurring revenue. |
|
| License-key admin (activate, deactivate, disable, change activation limit or expiry). |
|
| Webhook configuration — affects the trust surface other systems rely on. |
|
That is every tool's class. src/tools/tools.test.ts pins the same map, and moving a tool to another class is a MAJOR change under SEMVER.md.
Note: pii gates only the five customer tools above. It does not keep customer data out of an agent's reach: read-class tools still return customer names and emails incidentally (orders, subscriptions and license keys carry them), ls_list_orders and ls_list_subscriptions accept a userEmail filter, and the order, subscription and license-key get/list tools accept include=customer, which side-loads the customer record. The class is reserved for tools whose primary purpose is the customer record, so LEMONSQUEEZY_DISABLE_CLASSES=pii blocks those tools, not customer data. To keep customer data away from an agent, issue its API key from a separate LemonSqueezy account that doesn't host customer records you care about.
The License API tools are outside the API-key boundary. ls_activate_license, ls_validate_license and ls_deactivate_license authenticate with the license key the caller passes, not the API key, so they work on any LemonSqueezy account's license keys, whichever API key the server holds. A separate LemonSqueezy account does not fence them off, and LEMONSQUEEZY_ALLOWED_STORE_IDS does not gate them. The controls that do reach them, per tool:
ls_deactivate_license(classkey):LEMONSQUEEZY_DISABLE_CLASSES=key,LEMONSQUEEZY_RATE_LIMIT_PER_CLASS=key:N, andLEMONSQUEEZY_DESTRUCTIVE_RATE_LIMIT, which treats every call as destructive. Every call is also audited, with the license key masked (see Resources).ls_activate_license(classkey): the same two class controls. Activation is additive, so it is not destructive, and the destructive rate limit and the audit log do not see it.ls_validate_license(classread): onlyLEMONSQUEEZY_DISABLE_CLASSES=readorLEMONSQUEEZY_RATE_LIMIT_PER_CLASS=read:N, and both of those also hit every other read. The read-only lock-down below leaves it reachable.
Both key controls also cover ls_update_license_key, the management-API tool in the same class.
The two opt-in env vars that consume this taxonomy:
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.LEMONSQUEEZY_DISABLE_CLASSES— blocks a class outright. Useful when fast deploy-time toggles matter — flippingDISABLE_CLASSES=pii,mutate,money,recurring,key,webhookto lock an analytics deployment into pure reads is a one-line config change (everyreadtool stays reachable,ls_validate_licenseincluded). 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; that does not bound the License API tools.
Both are opt-in; with neither set, every class is allowed and unlimited.
Resources
The server exposes one MCP Resource for clients that prefer structural retrieval over parsing stderr:
URI | MIME type | Contents |
|
| 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. Inputs are redacted before they reach the buffer, and the stderr log gets the same redacted copy. (1) Any object key whose whole name matches a credential / PII pattern ( |
Operating the server unattended
For unattended/agentic use against a live store, we recommend:
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_IDSto the same set as a belt-and-braces in-process gate on the tools that take astoreId. Account separation does not bound the three License API tools, which authenticate with a license key rather than the API key (see Authority classes).Set
LEMONSQUEEZY_MAX_REFUND_AMOUNT_CENTSto a per-call cap well below any single-refund expectation.Set
LEMONSQUEEZY_DESTRUCTIVE_RATE_LIMITto a small number (e.g. 5/min) as a runaway-agent circuit breaker. For finer control, addLEMONSQUEEZY_RATE_LIMIT_PER_CLASS=money:2/h,recurring:5/h,key:10/mso each authority class has its own ceiling. If the agent also reports metered usage, size therecurringfigure for that too: usage reports count against it.If a class shouldn't be reachable at all (e.g. an analytics agent that needs only
read), setLEMONSQUEEZY_DISABLE_CLASSESto 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 (the License API tools excepted).Set
LEMONSQUEEZY_LOG=auditand ship stderr to your log aggregator. Theauditlevel keeps every destructive-call entry plus errors but drops successful reads so log volume stays bounded over weeks of operation. Alert onstatus: "guardrail_block"or elevated error rates per tool. UseLEMONSQUEEZY_LOG=allwhile debugging.Run
LEMONSQUEEZY_API_KEY_COMMANDagainst 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.
What the server does not do and you must own at the caller level:
Idempotency / dedupe store — MCP servers are stateless subprocesses; cross-invocation dedupe belongs in your agent or orchestrator.
Webhook reconciliation — subscribe to LemonSqueezy webhooks in a separate long-running process to reconcile state when API writes succeed but the response is lost. See @yawlabs/lemonsqueezy-webhook-sink for a ready-made sink.
Metrics / dashboards — the server emits structured logs; derive metrics in your log pipeline.
See SEMVER.md for the versioning policy.
Development
npm install
npm run lint
npm test # full unit + handler suite (builds first)
npm run test:integration # live suite; exits 1 unless LEMONSQUEEZY_TEST_API_KEY + LEMONSQUEEZY_TEST_STORE_ID are setThis repo has no CI: nothing re-runs lint, types or tests on a pull request, so run them locally. npm run test:integration writes throwaway ci-test- resources to a real store.
Containerfile is generated from Dockerfile. After editing Dockerfile:
npm run gen:containerfile # regenerate Containerfile
npm run check:containerfile # release.sh step 1 runs this too; non-zero exit means the two have driftedRunning on oam.js (optional)
oam.js runs this server unmodified. Verified against oam 0.9.0: full MCP handshake, all 64 tools, the lemonsqueezy://audit-log resource, working fetch, and guardrail rejections with error text identical to Node. On the published oam 0.19.5 the MCP handshake, all 64 tools (the same 64 as on Node), a tool call, and the audit-log resource have been re-verified through the launcher, with and without the sandbox below.
oam 0.19.5 is the minimum. The launcher picks the newest oam it can find at or above it, never serves on an older one, and falls back to Node when there is none (LEMONSQUEEZY_MCP_RUNTIME=oam turns that into a hard error). A floor matters here: releases before 0.9.0 ran child_process.execFile arguments through a shell, which was reachable whenever LEMONSQUEEZY_API_KEY_COMMAND is configured -- that feature runs the key command with execFile, deliberately without a shell, and on those releases its arguments were re-split by one anyway.
The published lemonsqueezy-mcp command (bin/lemonsqueezy-mcp.mjs, which is what npx runs) chooses its runtime from these variables:
Variable | Default | Purpose |
|
|
|
| unset | Path to an oam binary to use in preference to discovery, when it is 0.19.5 or newer. If it does not exist, is older, or will not run, the launcher says so on stderr and carries on with discovery. Discovery looks in |
| unset |
|
Sandboxing (opt-in)
Set LEMONSQUEEZY_MCP_SANDBOX=1 to run under oam's --permission model: network restricted to api.lemonsqueezy.com:443 (plus the host and port of LEMONSQUEEZY_SINK_URL when set -- the port its URL names, or 80 / 443 for http: / https:), filesystem denied outright, and child-process denied unless LEMONSQUEEZY_API_KEY_COMMAND is configured. The grants are port-scoped because from oam 0.18.0 a host:port entry admits fetch to that port (up to 0.17.1 it admitted no HTTP request, which is why the grant used to name bare hosts).
With LEMONSQUEEZY_API_KEY_COMMAND configured, two more things change, both from oam 0.18.0:
The key command's environment. A child started without an explicit environment gets only the variables
--allow-envgrants, and on Windows even the ones a child normally inherits (SYSTEMROOT,TEMP,USERPROFILE, ...) are filtered the same way. So the grant also namesAPPDATA,HOME,HOMEDRIVE,HOMEPATH,LANG,LOCALAPPDATA,SYSTEMDRIVE,SYSTEMROOT,TEMP,TMP,TMPDIR,USERPROFILEandWINDIR, which vault CLIs and PowerShell need. Measured on oam 0.18.0: without thempowershell -NoProfile -Command ...failed with "Internal Windows PowerShell error"; with them it,cmd /c ...andsh -c ...key commands all returned a key and the API answered on 443.A Node key command needs a wrapper. oam appends the server's
--permission/--allow-*flags to the child'sNODE_OPTIONS, as Node does for its own children, and Node 22 refuses--allow-netthere (node: --allow-net= is not allowed in NODE_OPTIONS, exit 9). Clear it first:sh -c 'unset NODE_OPTIONS; exec node get-key.js', or on Windowscmd /c "set NODE_OPTIONS=&& node get-key.js"(both measured working). The launcher prints this advice on stderr when the key command is a barenode. SettingNODE_OPTIONS=--permissionyourself does not help: the child would run under Node's permission model with no read grant for its own script.
When the launcher, running on oam, hands the server to Node (LEMONSQUEEZY_MCP_RUNTIME=node, or an oam below the floor), it takes --permission and --allow-* out of an inherited NODE_OPTIONS first and names what it dropped on stderr: Node cannot apply oam's net and env grants, and with them it would exit 9 before serving.
--permission is a process-level flag, so the launcher always starts a fresh oam for it -- even when a client already launches the command under oam 0.19.5+. That is a preference, not a guarantee: if no usable oam can be started, LEMONSQUEEZY_MCP_RUNTIME=auto still serves the server without --permission, and LEMONSQUEEZY_MCP_RUNTIME=oam exits with an error instead.
It is opt-in rather than default because a wrong grant does not fail loudly. oam denies a non-granted environment variable by making it absent from process.env rather than throwing, so an under-granted LEMONSQUEEZY_API_KEY reads as "unauthenticated" rather than "denied". The env allow-list in the launcher is derived from what the shipped bundle actually reads -- if you add a new process.env lookup, extend that list with it.
{
"mcpServers": {
"lemonsqueezy": {
"command": "oam",
"args": ["run", "/path/to/lemonsqueezy-mcp/dist/index.js"],
"env": { "LEMONSQUEEZY_API_KEY": "..." }
}
}
}Note the -- separator if you pass arguments to the server rather than to oam: oam run dist/index.js -- version.
The published command prefers oam; it does not require it. oam is not distributed on npm, so npx @yawlabs/lemonsqueezy-mcp has to keep working without it, and it does: discovery is file-existence checks only, never a subprocess, and the fallback runs the server inside the Node process npm already started. With oam installed, though, the command boots Node, runs --version on every oam binary it found to pick the newest, and only then boots oam, so it is always slower than pointing your client at oam directly with the config above. LEMONSQUEEZY_MCP_RUNTIME=node skips oam entirely. An MCP client cold-starts this server once per session, so startup is the cost that actually gets paid -- and it has not been measured for this repo on a current oam. Measure on your own hardware before concluding anything.
Two places oam does win for this repo, both opt-in and neither touching the npm package:
npm run check:oam— type-checks viaoam check(tsgo, TypeScript 7 native). Measured 2878ms against 4406ms fortsc --noEmit, same clean result.npx tsc --noEmitremains the portable default and is what the pre-commit checklist calls for.npm run build:binary:oam— builds the standalone binary viaoam compile(scripts/build-binary-oam.mjs). Measured 57.14 MB. Writes tobin/<platform>-<arch>/, whichscripts/stage-release-asset.mjsthen stages as a release asset. If you redistribute that binary it embeds oam's runtime, so ship oam'sLICENSE,NOTICEandTHIRD_PARTY_LICENSES.mdwith it.
The source stays runtime-agnostic on purpose: no oam: imports anywhere, and tests stay on node:test. That is what keeps the Node fallback real rather than nominal — an oam:test or oam:-prefixed import would make "falls back to Node" false the moment it landed. Any oam invocation writes a bytecode cache to oam/ in the working directory; that path is gitignored.
Releasing
Releases run locally, from a clean checkout of main, with release.sh. This repo has no GitHub Actions workflows (.github/ holds only CODEOWNERS), so pushing a tag by itself publishes nothing. release.sh keeps a CI mode for a tag-triggered release workflow, the intended end state, but nothing invokes it today.
Before cutting a release, write the ## [Unreleased] entry in CHANGELOG.md by hand, and run npm run test:integration against a test-mode store if src/api.ts or a tool handler changed (it needs LEMONSQUEEZY_TEST_API_KEY and LEMONSQUEEZY_TEST_STORE_ID, and exits 1 without them). The unit suite mocks fetch, so it cannot see changes in the upstream API. Then:
./release.sh X.Y.ZBefore step 1, release.sh checks that gh is installed and authenticated and that you are on main, and refuses a dirty working tree unless it is resuming a run whose version bump already landed. Unless X.Y.Z is already on npm, it also runs npm whoami and stops if that fails, so a dead or missing npm token ends the run before step 4 pushes the tag. npm whoami proves the token is live, not that it is an automation token: a web-login session passes it and then fails step 5 with EOTP.
It then runs eight steps on the workstation:
npm run lint,npm run check:containerfile(fails ifContainerfilehas drifted fromDockerfile), andscripts/check-oam-floor.mjs, which fails if the oam floor (OAM_MIN) is quoted differently anywhere in the launcher, README or launcher test, or is behind the latest oam release (LEMONSQUEEZY_MCP_ALLOW_STALE_OAM=1releases on the old floor deliberately; an unreachable GitHub API is reported and does not fail it)npm test(which builds first), thennpm run check:compliance: @yawlabs/mcp-compliance, pinned to the version line Yaw MCP grades with, grades the built server through the launcher on Node. A failed required check or a grade belowLEMONSQUEEZY_MCP_MIN_COMPLIANCE(defaultA) fails the release; skipped checks and the suite's warnings are printed asWARNINGlines. If the suite cannot run at all it says so and the release carries on, unlessLEMONSQUEEZY_MCP_COMPLIANCE_STRICT=1Bump the version in
package.jsonandpackage-lock.json, and syncserver.jsonto itTurn
## [Unreleased]inCHANGELOG.mdinto## [X.Y.Z](when[Unreleased]is empty, write that section from the commit subjects since the previous tag instead, with a warning), commit, create the annotated tagvX.Y.Z, and pushmainwith the tagnpm publish --access publicCreate the GitHub release, with the notes taken from the
## [X.Y.Z]section ofCHANGELOG.mdWait for npm to serve the new version (up to 10 minutes, then carry on with a warning), smoke-test
npx -y @yawlabs/lemonsqueezy-mcp@X.Y.Z --version(up to 60 attempts 10 seconds apart), then publishserver.jsonto the Official MCP Registry withmcp-publisherVerify the npm version,
package.jsonand the tag
Re-running after a partial failure is safe. ./release.sh X.Y.Z detects the version bump, commit, tag, npm publish and GitHub release that already landed and skips them; lint, test, the push and the npx smoke test run again. The MCP Registry publish in step 7 is not skipped, so a re-run after it has succeeded fails there on the duplicate version.
A workstation publish carries no npm provenance attestation: release.sh passes --provenance only in CI mode.
One-time setup on each machine:
An npm automation token for a publisher of
@yawlabs/lemonsqueezy-mcp, in~/.npmrc(npmjs.com -> Access Tokens -> Generate -> Automation):@yawlabs:registry=https://registry.npmjs.org/ //registry.npmjs.org/:_authToken=npm_YOURTOKENNever run
npm login --auth-type=web. It overwrites the automation token with a 2FA-bound web session, and the next publish fails withEOTP.gh auth login. The pre-flight requires it, and step 6 creates the release with it. Step 7 logsmcp-publisherin with its token (gh auth token) unlessMCP_REGISTRY_TOKENis set; either way the registry grants theio.github.YawLabs/*namespace only to a YawLabs org Owner whose token can read org roles (read:orgdoes; so dorepo,user,write:organdadmin:org). It reads the role at login but refuses only at publish, so a token that cannot read it still logs in and the publish gets a 403.mcp-publisheris downloaded to~/.local/binon first use; setMCP_PUBLISHERto use another copy.jq,curlandtaronPATH. Step 3 editsserver.jsonwithjq. Step 7 downloadsmcp-publisherwithcurlandtaron first use, unlessMCP_PUBLISHERpoints at an existing copy. It also polls npm withcurl, and skips that wait with a warning ifcurlis missing.Rights to push to
mainand createv*tags. The repository rulesets require a pull request formainand restrictv*tag creation, only organization admins bypass them, and step 4 pushes both directly.
License
MIT
Available Tools
64 toolsls_activate_licenseA
Activate a license key for an instance. Does not require an API key — uses the license key itself for auth.
Inputs: licenseKey (required), instanceName (required).
ls_archive_customerADestructiveIdempotent
Archive a customer. Sets their status to 'archived'. This is reversible by updating their status back.
Inputs: customerId (required).
ls_cancel_subscriptionADestructiveIdempotent
Cancel a subscription. The subscription remains active until the end of the current billing period, then expires.
Inputs: subscriptionId (required).
ls_create_checkoutA
Create a new checkout URL for a product variant. Returns a URL where the customer can complete their purchase. Supports custom pricing, prefilled customer data, and checkout customization.
Inputs: storeId (required), variantId (required), name, email, +8 more.
ls_create_customerA
Create a new customer in a store.
Inputs: name (required), email (required), storeId (required), city, +2 more.
ls_create_discountB
Create a new discount code. Supports percentage or fixed amount discounts with optional duration and usage limits.
Inputs: code (required), name (required), amount (required), storeId (required), +8 more.
ls_create_usage_recordADestructive
Report usage for a metered subscription item. Use 'increment' action to add to the current usage, or 'set' to replace it.
Inputs: quantity (required), subscriptionItemId (required), action.
ls_create_webhookA
Create a new webhook to receive event notifications. The signing secret is returned only once — save it immediately.
Inputs: url (required), events (required), secret (required), storeId (required).
ls_deactivate_licenseADestructiveIdempotent
Deactivate a license key instance, revoking that instance's access. Destructive: rate-limited and audited, with the license key masked in the audit entry. Does not require an API key — uses the license key itself for auth.
Inputs: instanceId (required), licenseKey (required).
ls_delete_discountADestructiveIdempotent
Permanently delete a discount. This is irreversible.
Inputs: discountId (required).
ls_delete_webhookADestructiveIdempotent
Permanently delete a webhook. This is irreversible.
Inputs: webhookId (required).
ls_generate_order_invoiceBIdempotent
Generate a PDF invoice for an order. Returns a download URL for the invoice.
Inputs: orderId (required), city, name, notes, +5 more.
ls_generate_subscription_invoiceAIdempotent
Generate a PDF invoice for a subscription invoice. Returns a download URL.
Inputs: subscriptionInvoiceId (required), city, name, notes, +5 more.
ls_get_affiliateARead-onlyIdempotent
Get a specific affiliate by ID, including commission rate, status, and earnings.
Inputs: affiliateId (required), include.
ls_get_checkoutARead-onlyIdempotent
Get a specific checkout by ID, including URL, expiry, and custom data.
Inputs: checkoutId (required), include.
ls_get_customerARead-onlyIdempotent
Get a specific customer by ID, including name, email, city, country, MRR, total revenue, and customer portal URL.
Inputs: customerId (required), include.
ls_get_discountARead-onlyIdempotent
Get a specific discount by ID, including code, amount, type, and usage limits.
Inputs: discountId (required), include.
ls_get_discount_redemptionARead-onlyIdempotent
Get a specific discount redemption by ID, showing when and where a discount was used.
Inputs: discountRedemptionId (required), include.
ls_get_fileARead-onlyIdempotent
Get a specific file by ID, including name, size, download URL, and associated variant.
Inputs: fileId (required), include.
ls_get_license_keyARead-onlyIdempotent
Get a specific license key by ID, including key value, status, activation limit, and expiry date.
Inputs: licenseKeyId (required), include.
ls_get_license_key_instanceARead-onlyIdempotent
Get a specific license key instance (activation) by ID, including instance name and creation date.
Inputs: licenseKeyInstanceId (required), include.
ls_get_orderARead-onlyIdempotent
Get a specific order by ID, including status, total, currency, customer info, and payment details.
Inputs: orderId (required), include.
ls_get_order_itemARead-onlyIdempotent
Get a specific order item by ID, including product name, variant, price, and quantity.
Inputs: orderItemId (required), include.
ls_get_priceARead-onlyIdempotent
Get a specific price by ID, including amount, currency, and billing interval. Every record is annotated with effective_unit_price (cents actually charged per unit) and effective_unit_price_note. READ THAT, not unit_price: on a tiered scheme (volume/graduated) unit_price is vestigial and is NOT the charged amount -- the real per-unit price lives in tiers[], and such records also carry unit_price_is_not_charged: true. On package pricing unit_price IS charged, but it buys a block of package_size units, so effective_unit_price reports the per-unit figure and the record carries unit_price_is_per_package: true.
Inputs: priceId (required), include.
ls_get_productARead-onlyIdempotent
Get a specific product by ID, including name, description, price, and status.
Inputs: productId (required), include.
ls_get_storeARead-onlyIdempotent
Get a specific store by ID, including name, slug, currency, and sales statistics.
Inputs: storeId (required), include.
ls_get_subscriptionARead-onlyIdempotent
Get a specific subscription by ID, including status, billing interval, renewal date, and customer info.
Inputs: subscriptionId (required), include.
ls_get_subscription_invoiceARead-onlyIdempotent
Get a specific subscription invoice by ID, including status, total, billing reason, and payment details.
Inputs: subscriptionInvoiceId (required), include.
ls_get_subscription_itemARead-onlyIdempotent
Get a specific subscription item by ID, including quantity, pricing, and associated subscription. Pass include=price for the price record behind it; embedded price records are annotated with effective_unit_price (cents actually charged per unit) -- read that, not the record's unit_price, which is vestigial on tiered pricing and per-package on package pricing.
Inputs: subscriptionItemId (required), include.
ls_get_subscription_item_usageARead-onlyIdempotent
Get the current usage for a metered subscription item within the current billing period.
Inputs: subscriptionItemId (required).
ls_get_usage_recordARead-onlyIdempotent
Get a specific usage record by ID, including quantity and action type.
Inputs: usageRecordId (required), include.
ls_get_userARead-onlyIdempotent
Get the authenticated user's information including name, email, and avatar.
Inputs: no named parameters.
ls_get_variantARead-onlyIdempotent
Get a specific product variant by ID, including billing interval and trial settings. WARNING: do NOT read price as the amount charged. For usage-based, per-seat or otherwise tiered variants it is vestigial and commonly reads the same value across products that bill very differently. The authoritative per-unit price lives on the variant's PRICE resource -- call ls_list_prices with this variantId, take the newest record by created_at, and read its effective_unit_price.
Inputs: variantId (required), include.
ls_get_webhookARead-onlyIdempotent
Get a specific webhook by ID, including URL, events, and last sent timestamp.
Inputs: webhookId (required), include.
ls_list_affiliatesARead-onlyIdempotent
List all affiliates for the authenticated user's stores, optionally filtered by user email. Results are paginated — check meta.page in the response for currentPage, lastPage, and total. Cross-store note: LEMONSQUEEZY_ALLOWED_STORE_IDS does NOT gate this tool -- it has no storeId field and no parent ID filter to scope by, so it returns affiliates tied to every store the API key can see, including non-allowed ones. Pair with a scoped LemonSqueezy API key for true cross-store enforcement -- the API key's visibility is the true boundary.
Inputs: include, pageSize, userEmail, pageNumber.
ls_list_checkoutsARead-onlyIdempotent
List all checkouts, optionally filtered by store or variant. Results are paginated — check meta.page in the response for currentPage, lastPage, and total.
Inputs: include, storeId, pageSize, variantId, +1 more.
ls_list_customersARead-onlyIdempotent
List all customers, optionally filtered by store or email. Results are paginated — check meta.page in the response for currentPage, lastPage, and total.
Inputs: email, include, storeId, pageSize, +1 more.
ls_list_discount_redemptionsARead-onlyIdempotent
List all discount redemptions, optionally filtered by discount or order. Results are paginated — check meta.page in the response for currentPage, lastPage, and total. Cross-store note: when LEMONSQUEEZY_ALLOWED_STORE_IDS is set, this tool requires at least one of: discountId, orderId. Even with that set, Pair with a scoped LemonSqueezy API key for true cross-store enforcement -- the API key's visibility is the true boundary.
Inputs: include, orderId, pageSize, discountId, +1 more.
ls_list_discountsARead-onlyIdempotent
List all discounts, optionally filtered by store. Results are paginated — check meta.page in the response for currentPage, lastPage, and total.
Inputs: include, storeId, pageSize, pageNumber.
ls_list_filesARead-onlyIdempotent
List all files, optionally filtered by variant. Results are paginated — check meta.page in the response for currentPage, lastPage, and total. Cross-store note: when LEMONSQUEEZY_ALLOWED_STORE_IDS is set, this tool requires at least one of: variantId. Even with that set, Pair with a scoped LemonSqueezy API key for true cross-store enforcement -- the API key's visibility is the true boundary.
Inputs: include, pageSize, variantId, pageNumber.
ls_list_license_key_instancesARead-onlyIdempotent
List all license key instances (activations), optionally filtered by license key. Results are paginated — check meta.page in the response for currentPage, lastPage, and total. Cross-store note: when LEMONSQUEEZY_ALLOWED_STORE_IDS is set, this tool requires at least one of: licenseKeyId. Even with that set, Pair with a scoped LemonSqueezy API key for true cross-store enforcement -- the API key's visibility is the true boundary.
Inputs: include, pageSize, pageNumber, licenseKeyId.
ls_list_license_keysARead-onlyIdempotent
List all license keys, optionally filtered by store, order, or product. Results are paginated — check meta.page in the response for currentPage, lastPage, and total.
Inputs: include, orderId, storeId, pageSize, +3 more.
ls_list_order_itemsARead-onlyIdempotent
List all order items, optionally filtered by order or product. Results are paginated — check meta.page in the response for currentPage, lastPage, and total. Cross-store note: when LEMONSQUEEZY_ALLOWED_STORE_IDS is set, this tool requires at least one of: orderId, productId, variantId. Even with that set, Pair with a scoped LemonSqueezy API key for true cross-store enforcement -- the API key's visibility is the true boundary.
Inputs: include, orderId, pageSize, productId, +2 more.
ls_list_ordersARead-onlyIdempotent
List all orders, optionally filtered by store or user email. Results are paginated — check meta.page in the response for currentPage, lastPage, and total.
Inputs: include, storeId, pageSize, userEmail, +1 more.
ls_list_pricesARead-onlyIdempotent
List all prices, optionally filtered by variant. Results are paginated — check meta.page in the response for currentPage, lastPage, and total. Every record is annotated with effective_unit_price (cents actually charged per unit) and effective_unit_price_note. READ THAT, not unit_price: on a tiered scheme (volume/graduated) unit_price is vestigial and is NOT the charged amount -- the real per-unit price lives in tiers[], and such records also carry unit_price_is_not_charged: true. On package pricing unit_price IS charged, but it buys a block of package_size units, so effective_unit_price reports the per-unit figure and the record carries unit_price_is_per_package: true. NOTE that several price records can exist per variant; the CURRENT one is the newest by created_at (results are sorted newest-first). Cross-store note: when LEMONSQUEEZY_ALLOWED_STORE_IDS is set, this tool requires at least one of: variantId. Even with that set, Pair with a scoped LemonSqueezy API key for true cross-store enforcement -- the API key's visibility is the true boundary.
Inputs: include, pageSize, variantId, pageNumber.
ls_list_productsARead-onlyIdempotent
List all products, optionally filtered by store. Results are paginated — check meta.page in the response for currentPage, lastPage, and total.
Inputs: include, storeId, pageSize, pageNumber.
ls_list_storesARead-onlyIdempotent
List all stores for the authenticated user. Results are paginated — check meta.page in the response for currentPage, lastPage, and total. Cross-store note: LEMONSQUEEZY_ALLOWED_STORE_IDS does NOT gate this tool -- it has no storeId field and no parent ID filter to scope by, so it returns every store the API key can see, including stores outside the allowlist. Pair with a scoped LemonSqueezy API key for true cross-store enforcement -- the API key's visibility is the true boundary.
Inputs: include, pageSize, pageNumber.
ls_list_subscription_invoicesARead-onlyIdempotent
List all subscription invoices, optionally filtered by store, subscription, or status. Results are paginated — check meta.page in the response for currentPage, lastPage, and total.
Inputs: status, include, storeId, pageSize, +3 more.
ls_list_subscription_itemsARead-onlyIdempotent
List all subscription items, optionally filtered by subscription or price. Results are paginated — check meta.page in the response for currentPage, lastPage, and total. Pass include=price for the price records behind them; embedded price records are annotated with effective_unit_price (cents actually charged per unit) -- read that, not a record's unit_price, which is vestigial on tiered pricing and per-package on package pricing. Cross-store note: when LEMONSQUEEZY_ALLOWED_STORE_IDS is set, this tool requires at least one of: subscriptionId, priceId. Even with that set, Pair with a scoped LemonSqueezy API key for true cross-store enforcement -- the API key's visibility is the true boundary.
Inputs: include, priceId, pageSize, pageNumber, +1 more.
ls_list_subscriptionsARead-onlyIdempotent
List all subscriptions, optionally filtered by store, order, product, variant, or status. Results are paginated — check meta.page in the response for currentPage, lastPage, and total.
Inputs: status, include, orderId, storeId, +6 more.
ls_list_usage_recordsARead-onlyIdempotent
List all usage records, optionally filtered by subscription item. Results are paginated — check meta.page in the response for currentPage, lastPage, and total. Cross-store note: when LEMONSQUEEZY_ALLOWED_STORE_IDS is set, this tool requires at least one of: subscriptionItemId. Even with that set, Pair with a scoped LemonSqueezy API key for true cross-store enforcement -- the API key's visibility is the true boundary.
Inputs: include, pageSize, pageNumber, subscriptionItemId.
ls_list_variantsARead-onlyIdempotent
List all variants, optionally filtered by product. Results are paginated — check meta.page in the response for currentPage, lastPage, and total. WARNING: do NOT read price as the amount charged -- for usage-based, per-seat or otherwise tiered variants it is vestigial and commonly reads the same value across products that bill very differently. The authoritative per-unit price lives on each variant's PRICE resource: call ls_list_prices with the variantId, take the newest record by created_at, and read its effective_unit_price. Cross-store note: when LEMONSQUEEZY_ALLOWED_STORE_IDS is set, this tool requires at least one of: productId. Even with that set, Pair with a scoped LemonSqueezy API key for true cross-store enforcement -- the API key's visibility is the true boundary.
Inputs: include, pageSize, productId, pageNumber.
ls_list_webhooksARead-onlyIdempotent
List all webhooks, optionally filtered by store. Results are paginated — check meta.page in the response for currentPage, lastPage, and total.
Inputs: include, storeId, pageSize, pageNumber.
ls_refund_orderADestructive
Issue a refund for an order. This is irreversible — the refund amount is in cents (e.g. 1000 = $10.00).
Inputs: amount (required), orderId (required).
ls_refund_subscription_invoiceADestructive
Issue a refund for a subscription invoice. This is irreversible — the refund amount is in cents (e.g. 1000 = $10.00).
Inputs: amount (required), subscriptionInvoiceId (required).
ls_sink_event_mark_processedAIdempotent
Mark a sink event as processed by your consumer. Idempotent.
Inputs: id (required).
ls_sink_events_listARead-onlyIdempotent
List webhook events the sink has received, optionally filtered. Use since (received_at timestamp, exclusive) to checkpoint. Requires the sink at LEMONSQUEEZY_SINK_URL with LEMONSQUEEZY_SINK_ADMIN_TOKEN.
Inputs: type, limit, since.
ls_sink_statsARead-onlyIdempotent
Get sink totals: total events, unprocessed count, last-received timestamp.
Inputs: no named parameters.
ls_update_customerADestructiveIdempotent
Update an existing customer's name, email, city, region, country, or status. The only supported status value is 'archived'; setting it here is the same operation as ls_archive_customer and is treated as destructive (rate-limited and audited). Other field edits are not.
Inputs: customerId (required), city, name, email, +3 more.
ls_update_license_keyADestructiveIdempotent
Update a license key's activation limit, expiry date, or disabled status. Setting disabled: true, changing activationLimit, or changing expiresAt can revoke customer access and is treated as destructive (rate-limited and audited); disabled: false alone is not.
Inputs: licenseKeyId (required), disabled, expiresAt, activationLimit.
ls_update_subscriptionADestructiveIdempotent
Update a subscription. Can change the variant (plan switch), pause/unpause, set billing anchor, or update invoice details. Pausing (pause: 'void' or 'free'), switching plan (variantId), changing the billing anchor (billingAnchor), invoicing immediately (invoiceImmediately: true), or changing the trial end (trialEndsAt) changes what the customer pays or when, and is treated as destructive (rate-limited and audited); resuming (pause: 'resume'), un-cancelling (cancelled: false), invoiceImmediately: false and disableProrations are not. Use ls_cancel_subscription for cancellation.
Inputs: subscriptionId (required), pause, cancelled, variantId, +4 more.
ls_update_subscription_itemADestructiveIdempotent
Update a subscription item's quantity. Used for seat-based or quantity-based billing.
Inputs: quantity (required), subscriptionItemId (required).
ls_update_webhookADestructiveIdempotent
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).
Inputs: webhookId (required), url, events, secret.
ls_validate_licenseARead-onlyIdempotent
Validate a license key or specific instance. Does not require an API key — uses the license key itself for auth.
Inputs: licenseKey (required), instanceId.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 64 tools
Most tools target distinct resources with clear get/list/create/update/delete boundaries. The only notable overlap is ls_archive_customer vs. ls_update_customer's status field, but the descriptions explicitly call this out home. Sink and license tools are clearly separated.
All tools follow the pattern ls_verb_noun consistently, using standard verbs like get_, list_, create_, update_, delete_. The only slight deviations are the two sink tools (ls_sink_events_list, ls_sink_event_mark_processed), but they still retain the prefix and a clear verb-noun structure.
With 64 tools, this server is far beyond the recommended 3-15 range and crosses the 50+ threshold for extreme mismatch. While the scope justifies many resources, the sheer volume imposes significant cognitive load on agents and makes tool selection error-prone.
The surface covers most lifecycle operations for orders, subscriptions, customers, discounts, license keys, and webhooksherches, but product and variant creation/update are missing, forcing agents to rely on external means for catalog management. This is a notable gap for a full commerce server.
Maintenance
Related MCP Connectors
MCP server for Lemon Squeezy — stores, products, orders, subscriptions, license keys.
Hosted MCP server for 3Dsellers, the multichannel selling platform. Connect Claude, ChatGPT, Cursor, VS Code or any MCP client and manage your eBay, Amazon, Walmart, Etsy, Shopify, WooCommerce and TikTok Shop stores in plain language: search and update products, publish listings, sync inventory, tag and track orders, run bulk actions and CSV imports, and reply to customer messages. 120+ tools. Sign in with OAuth or a personal token. No eBay developer account and nothing to install. Setup guides and config examples for every client: https://github.com/3dsellers/ebay-mcp-server
MCP server for Shoprocket — orders, products, customers, store stats and discount codes.
Hosted MCP server connecting AI assistants to 9,000+ apps and 40,000+ actions via Zapier.
Related MCP Servers
- AlicenseBqualityCmaintenanceUniversal Semantic Bridge for Lemon Squeezy: A high-performance Model Context Protocol (MCP) server that empowers AI assistants (Cursor, Claude, VS Code) to query payments, manage subscriptions, and sync customers to Salesforce directly from your editor. 🍋✨5370 npm3MIT
- AlicenseBqualityDmaintenanceMCP server for managing WooCommerce stores through AI assistants like Claude. Provides 101 tools covering products, orders, customers, coupons, shipping, taxes, webhooks, settings, reports, and more.100112 npm2MIT
- AlicenseNot gradedqualityDmaintenanceA comprehensive Model Context Protocol (MCP) server that provides complete access to the Recharge Storefront API endpoints. Enables AI assistants to manage subscriptions, customers, orders, and billing through a standardized interface.2MIT
- AlicenseBqualityBmaintenanceMCP server for the Revolut Merchant API, enabling AI assistants to read and manage customers, orders, subscriptions, and plans. Supports sandbox and production with safe defaults.8MIT