Ask your agent about the store and let it act - orders, subscriptions, license keys, customers and refunds - with guardrails for the calls that move money.
@yawlabs/lemonsqueezy-mcp is an MCP server for the LemonSqueezy API. Manage your store, products, customers, subscriptions, discounts, license keys and more from any MCP-compatible AI assistant. It ships as a single bundled file with zero runtime dependencies for instant npx startup. From Yaw Labs, MIT licensed, published on npm.
npx -y @yawlabs/lemonsqueezy-mcp@latestGitHub · npm · Add to Yaw MCP
Anyone running a store on LemonSqueezy who would rather ask a question than click through the dashboard or hand-roll a script against the API. Once it is configured, store questions get answered in plain English and routed through the tools:
1. Set your API key. Create one in your LemonSqueezy dashboard under Settings > API:
export LEMONSQUEEZY_API_KEY="your-api-key"If you would rather the key never sit in a config file or an environment variable at all, set LEMONSQUEEZY_API_KEY_COMMAND instead - the server runs that command and uses its stdout, so you can pull a short-lived credential from a vault. See Safety and control for how the cache and rotation behave.
2. Add the server to your client. In Claude Code, your MCP config:
{
"mcpServers": {
"lemonsqueezy": {
"command": "npx",
"args": ["-y", "@yawlabs/lemonsqueezy-mcp@latest"],
"env": {
"LEMONSQUEEZY_API_KEY": "your-api-key"
}
}
}
}Claude Desktop takes the same block in claude_desktop_config.json. If your shell profile already exports the key, you can drop the env object entirely rather than duplicating the credential into a file your editor may sync.
3. Restart and approve. Restart Claude Code (or your MCP client) and approve the LemonSqueezy server when prompted. Then ask it something: "List the subscriptions that churned on this store last month."
Running Yaw MCP? One click adds it to your local config, and it is then available in every Yaw Terminal session.
Full coverage of the LemonSqueezy API resources, with JSON:API filtering, pagination and relationship inclusion on the list and get operations:
| Area | What it covers |
|---|---|
| Stores and users | The authenticated user, get a store, list stores |
| Customers | Get, list by store or email, create, update, archive |
| Catalog | Products, variants, prices and files - get and list, filtered by their parent |
| Orders | Get, list by store or email, order items, generate a PDF invoice, issue a refund |
| Subscriptions | Get, list by store, status or product, update (plan switch, pause, billing anchor, trial), cancel |
| Subscription invoices and items | Get and list invoices, generate a PDF, refund an invoice; update item quantity and read current billing-period usage |
| Usage records | Get, list by subscription item, and report metered usage as an increment or an absolute set |
| Discounts | Get, list, create and delete discount codes, plus redemptions by discount or order |
| License keys | Get and list keys, update the activation limit, expiry or disabled status, and list activations for a key |
| Checkouts | Get, list, and create a checkout URL with custom pricing, prefill and discounts |
| Webhooks | Get, list, create, update and delete |
| License API | Activate, validate and deactivate a license key - these need no API key at all |
| Webhook sink (optional) | Bridge to a separate webhook-sink process so the agent can reconcile against webhooks that actually fired: list received events, mark one processed, read totals and the last-received timestamp |
The sink tools are always registered. If the sink URL and admin token are not set they return a clear "not configured" error rather than vanishing from the tool list, so a model can tell the difference between a capability that does not exist and one that is not wired up yet.
One MCP resource is exposed for clients that prefer structural retrieval over parsing stderr: lemonsqueezy://audit-log serves the most recent destructive tool calls and their outcomes as application/x-ndjson, most-recent-first. It is a bounded ring buffer that resets on server restart - a live tail, not a durable audit store.
Start with the caveat the README leads with, because it decides how much the rest is worth: the strongest access control LemonSqueezy itself exposes is the API key boundary. A LemonSqueezy API key is issued at the account level and authorizes that whole account - every store, every action. There is no "this key can read but not refund" toggle. So the durable boundary is which key the agent holds; everything below is an in-process control surface for what LemonSqueezy cannot gate itself, and an operator who can change the server's environment can also remove it.
With that understood, the guardrails are all opt-in and compose:
read, pii (payload is a customer record), mutate (checkouts, discounts, invoices, usage), money (irreversible at the payment layer), recurring, key and webhook. A checkout and a refund are both writes; only one moves money.LEMONSQUEEZY_DISABLE_CLASSES refuses any tool in a listed class before the API call is built - locking an analytics deployment into pure reads is a one-line config change. Unknown class names throw at startup rather than silently granting.LEMONSQUEEZY_RATE_LIMIT_PER_CLASS takes entries like money:2/h,recurring:5/h,key:10/m. LemonSqueezy permissions cannot express "at most two refunds an hour", which makes this the load-bearing control against a runaway agent. Malformed entries throw at startup.LEMONSQUEEZY_MAX_REFUND_AMOUNT_CENTS rejects refunds above a ceiling, and 0 is a valid value that blocks every refund - a kill switch. The check runs before the rate limiters, so a rejected over-cap refund does not consume the budget.LEMONSQUEEZY_DESTRUCTIVE_RATE_LIMIT caps destructive calls per rolling minute, counting the input-dependent paths too - a license-key update that sets disabled or changes the activation limit, a subscription update that pauses or switches plan, a customer update that archives. Know the limit's shape: it is in-process, per server instance, so each npx cold start resets the window.LEMONSQUEEZY_ALLOWED_STORE_IDS rejects calls to a non-allowed store on any tool that takes a store ID, and forces a store filter on list tools that accept one so a missing filter cannot quietly return every store's data. But tools with no store ID field are not gated: ID-targeted calls like a refund or a cancellation route by their own resource ID, and ls_list_stores and ls_list_affiliates take no scoping ID at all and will enumerate every store the key can see. Both say so in their own tool descriptions.LEMONSQUEEZY_API_KEY_COMMAND pulls the key from a vault command's stdout and caches it for an hour, keyed by the command string. The cache is invalidated automatically on a 401 or 403, so a key rotated upstream takes effect on the next call instead of waiting out the TTL. A separate LEMONSQUEEZY_TEST_API_KEY points the server at a sandbox store without unsetting your production key, and prints a one-line notice to stderr the first time it engages.LEMONSQUEEZY_LOG=audit is the production setting - every destructive-call entry plus errors, dropping successful reads so volume stays flat over weeks. Failures carry a status naming the cause, so a policy refusal reads differently from a malformed request or a network error. Stdout is reserved for the MCP protocol; logs never go there.What the server deliberately does not do, and you own at the caller level: cross-invocation idempotency and dedupe (MCP servers are stateless subprocesses, so that belongs in your agent or orchestrator), webhook reconciliation (run a long-running sink and bridge to it), and metrics (derive them from the structured logs in your pipeline).
Docker or Podman. A multi-stage Dockerfile lives at the repo root. The runtime image is a single bundled file on Alpine running as a non-root user, with no port exposed - it speaks stdio:
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 the Dockerfile rather than maintained by hand, and CI fails review if the two have drifted.
Smithery. npx -y @smithery/cli install @yawlabs/lemonsqueezy-mcp --client claude prompts for your environment variables - the API key and any guardrails - and writes the config into your client for you.
oam.js. The server runs unmodified on oam, and LEMONSQUEEZY_MCP_SANDBOX=1 runs it under oam's permission model: network restricted to the LemonSqueezy API host, filesystem denied outright, child processes denied unless the API-key command is configured. Node stays the default deliberately - a client cold-starts this server once per session, and Node won that startup measurement on the hardware it was tested on. The sandbox is opt-in for a reason worth carrying: a wrong grant does not fail loudly, because oam denies a non-granted environment variable by making it absent rather than throwing, so an under-granted API key reads as "unauthenticated" rather than "denied".
It puts the LemonSqueezy API behind MCP tools, so you can ask store-management questions in plain English and have the agent act on the answer in the same turn. Stores, products, variants, prices, files, customers, orders, subscriptions and their invoices and items, usage records, discounts, license keys and activations, checkouts and webhooks are all covered, with JSON:API filtering, pagination and relationship inclusion on the list and get operations.
Set LEMONSQUEEZY_API_KEY from your LemonSqueezy dashboard. To avoid writing the key anywhere, set LEMONSQUEEZY_API_KEY_COMMAND instead: the server runs that command and uses its stdout, so you can pull a short-lived credential from a vault. The result is cached for an hour, keyed by the command string, and invalidated automatically on a 401 or 403 from the API, so a key rotated upstream takes effect on the next call rather than waiting out the cache.
Yes, with opt-in guardrails. Every tool carries an authority class - read, pii, mutate, money, recurring, key, webhook - and LEMONSQUEEZY_DISABLE_CLASSES refuses a whole class before the API call is built. LEMONSQUEEZY_RATE_LIMIT_PER_CLASS caps how often each class can be called, LEMONSQUEEZY_MAX_REFUND_AMOUNT_CENTS rejects refunds above a ceiling, and a value of zero on either the refund cap or the destructive rate limit blocks those calls entirely as a kill switch.
No, and the README is direct about it. LemonSqueezy issues API keys at the account level, and a key authorizes every store in that account, so LEMONSQUEEZY_ALLOWED_STORE_IDS is an in-process gate rather than a real boundary. It blocks calls to a non-allowed store on tools that take a store ID, but tools with no store ID field are not gated, and list stores and list affiliates return rows from every store the key can see. An operator who can change the server environment can also remove the gate. For a durable boundary, issue the agent's key from a separate LemonSqueezy account.
Issue the key under an account hosting only the stores the agent may touch, then set the store allowlist to match. Add a per-call refund cap well below any plausible single refund, a small destructive rate limit as a runaway circuit breaker, and per-class limits for money, recurring and key. Set LEMONSQUEEZY_LOG to audit and ship stderr to your log aggregator, alerting on guardrail blocks. Note the rate limits are per server instance, so each cold start resets the window.
Published by Yaw Labs.