@yawlabs/lemonsqueezy-mcp 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +25 -1
- package/README.md +10 -10
- package/bin/lemonsqueezy-mcp.mjs +2 -2
- package/dist/index.js +82 -18
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,28 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [1.0.2] — 2026-10-06
|
|
6
|
+
|
|
7
|
+
### Security
|
|
8
|
+
- **The bundled MCP SDK moves from 1.30.0 to 1.32.1** (GHSA-6qxp-vccf-f47h, high: the SDK's OAuth client could send credentials to an authorization server chosen by the MCP server; this server does not import that client, but the bundled SDK version changes). esbuild bundles the SDK into `dist/index.js`, so the dependency floor is now `^1.32.1`. Its `fast-uri`, also bundled through ajv, moves from 3.1.7 to 3.1.8 (GHSA-hrr3-gc8f-f4qj, moderate: inconsistent host case normalization via percent-encoded octets). Development-scope only: `proxy-addr` 2.0.8 (GHSA-jqcg-44mw-7w3h, critical) and `ip-address` 10.7.3 (GHSA-j6r3-76f7-8jcv, GHSA-h3mg-xc3c-68pw), which reach the tree through the SDK's Express transport but are not in the bundle. The existing `hono` override is unchanged. `npm audit` reports 0 vulnerabilities.
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
- **The oam floor moves from 0.15.2 to 0.18.0, so the `lemonsqueezy-mcp` launcher no longer runs the server on an older oam.** This server is verified on one oam release at a time, and the floor keeps the launcher off anything older than that release. 0.18.0 is verified on the published aarch64-pc-windows-msvc binary, checksum matched against the release SHA256SUMS, through the launcher with `OAM_BIN` pointing at it: a full MCP handshake listing all 64 tools (the same 64 as on Node), `ls_sink_stats` and the `lemonsqueezy://audit-log` resource answering byte-for-byte as they do on Node, and the server process confirmed as that binary (`oam run .../dist/index.js` as the launcher's child; `process.versions.oam` reads 0.18.0). The opt-in sandbox was re-measured on it: with `LEMONSQUEEZY_MCP_SANDBOX=1 LEMONSQUEEZY_MCP_RUNTIME=oam` the launcher spawns `oam --permission --allow-net=api.lemonsqueezy.com --allow-env=... run`, the same 64 tools answer as they do on Node, and under the same flags a file read and a child process are refused with `ERR_ACCESS_DENIED` while a non-granted environment variable is absent from `process.env` rather than an error, as the README describes. **If the launcher finds only an oam from 0.15.2 to 0.17.x, it now falls back to Node under the default `LEMONSQUEEZY_MCP_RUNTIME=auto`, and exits with an error under `LEMONSQUEEZY_MCP_RUNTIME=oam`** -- it says so on stderr, naming the version it found and the floor (measured with oam 0.17.0: `is oam 0.17.0, older than 0.18.0; using Node instead.`). On Node every tool answers the same; what is lost is `LEMONSQUEEZY_MCP_SANDBOX=1`, which needs a fresh oam at the floor, so under `auto` the server then runs without `--permission`, and under `LEMONSQUEEZY_MCP_RUNTIME=oam` it does not start. A client that runs `oam run /path/to/lemonsqueezy-mcp/dist/index.js` directly bypasses the launcher and keeps the oam it names. Run `oam self-update`, or set `LEMONSQUEEZY_MCP_RUNTIME=node` to make the choice explicit.
|
|
12
|
+
- `release.sh` waits up to 600 s, not 300, for npm to serve a new version before the MCP Registry step, gives the post-publish `npx` smoke 60 attempts 10 s apart, not 30, and polls npm up to 120 times 5 s apart in its final check instead of reading once after 3 s. On 2026-09-29 the @yawlabs/fetch-mcp 0.8.2 release spent 295 s of its 300 s gate waiting for npm to serve the new version, and `npx` needed 313 s, 24 of its 30 attempts, to install @yawlabs/lemonsqueezy-mcp 1.0.1. Release tooling only; the server itself is unchanged.
|
|
13
|
+
- `release.sh` runs every mcp-publisher call to the MCP Registry, each login and each publish attempt, under coreutils `timeout` (`MCP_PUBLISH_TIMEOUT_S`, default 90 s) where one (or Homebrew's `gtimeout`) is on PATH -- without one the call runs unbounded, and a warning says so -- because mcp-publisher waits for the registry's answer with no limit of its own and a registry that never answered would have hung the release. A publish attempt that gets no answer is retried on the same 30, 60 and 90 s clock as the registry's own 429-504: one the limit stopped, which used to hang the step, and one whose connection failed or dropped (the client's `error sending request` or `error reading response`), which used to fail it at once. A login the limit stopped says the registry did not answer; one that failed any other way says how to read mcp-publisher's output (a 401 is the registry refusing the token exchange; a 429, a 5xx or a connection error is the registry or the network) instead of naming token scopes, which never fail a login: the registry reads the org role at login and refuses only at publish. A publish refused with a 403 says what the io.github.YawLabs namespace takes: a YawLabs org Owner whose token can read org roles. Release tooling only; the server itself is unchanged.
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
- `scripts/update-manifests.mjs` escapes every value it writes into a Ruby string in the Homebrew formula (CodeQL js/incomplete-sanitization). The `desc` line used to escape `"` but not `\`, so a description holding `\"` came out as `\\"`, an escaped backslash and then a closing quote, and it left `#{...}` alone, which Ruby runs as code when brew loads the formula. A new `rubyString()` escapes the backslash first, then `"`, a `#` that starts `#{`, `#@` or `#$` (a plain `#` stays as written, so `brew style` sees no redundant escape), CR and LF, and covers `desc`, `homepage`, `version`, `license`, the download URLs and hashes, and the command name; the formula's class name is checked as a Ruby constant instead. The script body runs only when the file is executed directly (compared by real path, so a junction or symlink still runs it), which lets `src/update-manifests.test.ts` import the helpers and round-trip hostile descriptions through a Ruby double-quoted string reader. For the real package.json the generated formula and Scoop manifest are byte-identical to before; the Scoop manifest was already built with `JSON.stringify`. Release tooling only; the server itself is unchanged.
|
|
17
|
+
|
|
18
|
+
## [1.0.1] — 2026-09-29
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
|
|
22
|
+
- **`release.sh` reads npm's per-version document and retries the MCP Registry publish.** The "is this version on npm?" checks (the pre-flight token gate, step 5's already-published skip and the step 8 check) now ask `https://registry.npmjs.org/@yawlabs%2Flemonsqueezy-mcp/<version>` with `curl` instead of `npm view`, whose whole-package document Cloudflare's edge can serve stale for up to 5 minutes after a publish. The step 7 propagation wait already curled that URL; it now goes through the same check, so each request is capped at 20 s and only a 200 counts. Step 5 treats npm's E403 "cannot publish over the previously published versions" as already published rather than as a token error, so re-running straight after a failed later step no longer dies there. Step 7 makes up to 4 attempts at `mcp-publisher publish`, 30, 60 and 90 s apart, but only when the registry says it cannot see the version on npm yet, calls the failure transient, or answers HTTP 429, 502, 503 or 504 itself (with a fresh registry login before each retry); a duplicate version counts as done, and any other failure still stops at once.
|
|
23
|
+
|
|
24
|
+
### Documentation
|
|
25
|
+
- **The README's Add to Yaw MCP button now sits directly under the title, and the follow badge links to @YawLabs.** The one-click install button and its one-line caption moved above the one-paragraph description, so they are the first thing under the name on npm and GitHub, and the X badge at the bottom points at [@YawLabs](https://x.com/YawLabs) instead of @TokenLimitNews. npm shows the README from the published package, which is why it takes a release to carry this there; the package's code is unchanged from 1.0.0.
|
|
26
|
+
|
|
5
27
|
## [1.0.0] — 2026-09-18
|
|
6
28
|
|
|
7
29
|
**1.0.0 locks the public surface.** [SEMVER.md](./SEMVER.md) now spells out what that surface is: tool names and input fields, MCP annotations, the authority class names and each tool's class, which calls count as destructive, the documented env vars, the `lemonsqueezy://audit-log` resource, and the fields the server adds to responses. From here on its MAJOR / MINOR / PATCH rules apply strictly. `src/tools/tools.test.ts` pins the `destructiveHint: true` set, the `isDestructive`-predicate set, the `readOnlyHint: false` set and the full tool-to-class map, so a change to any of them fails `npm test` until it is made deliberately. The changes marked **Breaking** below were made now, before the lock, because each would need a major version afterwards. Tracked in #2.
|
|
@@ -676,7 +698,9 @@ Hardening pass for unattended automation against live billing flows.
|
|
|
676
698
|
|
|
677
699
|
Initial release. 59 tools covering all 17 LemonSqueezy API resources.
|
|
678
700
|
|
|
679
|
-
[Unreleased]: https://github.com/YawLabs/lemonsqueezy-mcp/compare/v1.0.
|
|
701
|
+
[Unreleased]: https://github.com/YawLabs/lemonsqueezy-mcp/compare/v1.0.2...HEAD
|
|
702
|
+
[1.0.2]: https://github.com/YawLabs/lemonsqueezy-mcp/compare/v1.0.1...v1.0.2
|
|
703
|
+
[1.0.1]: https://github.com/YawLabs/lemonsqueezy-mcp/compare/v1.0.0...v1.0.1
|
|
680
704
|
[1.0.0]: https://github.com/YawLabs/lemonsqueezy-mcp/compare/v0.14.2...v1.0.0
|
|
681
705
|
[0.14.2]: https://github.com/YawLabs/lemonsqueezy-mcp/compare/v0.14.1...v0.14.2
|
|
682
706
|
[0.14.1]: https://github.com/YawLabs/lemonsqueezy-mcp/compare/v0.14.0...v0.14.1
|
package/README.md
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
# @yawlabs/lemonsqueezy-mcp
|
|
2
2
|
|
|
3
|
-
MCP server for the [LemonSqueezy](https://lemonsqueezy.com) API. Manage your store, products, customers, subscriptions, discounts, license keys, and more from any MCP-compatible AI assistant.
|
|
4
|
-
|
|
5
3
|
[](https://yaw.sh/mcp/install?name=LemonSqueezy&command=npx&args=-y%2C%40yawlabs%2Flemonsqueezy-mcp&description=LemonSqueezy%20store%20management%20-%20products%2C%20orders%2C%20subscriptions%2C%20license%20keys&source=https%3A%2F%2Fgithub.com%2FYawLabs%2Flemonsqueezy-mcp)
|
|
6
4
|
|
|
7
5
|
One click adds this to your local Yaw MCP config so it's available in every Yaw Terminal session. Or install manually below.
|
|
8
6
|
|
|
7
|
+
MCP server for the [LemonSqueezy](https://lemonsqueezy.com) API. Manage your store, products, customers, subscriptions, discounts, license keys, and more from any MCP-compatible AI assistant.
|
|
8
|
+
|
|
9
9
|
## Quick start
|
|
10
10
|
|
|
11
11
|
```bash
|
|
@@ -338,23 +338,23 @@ npm run check:containerfile # release.sh step 1 runs this too; non-zero exit me
|
|
|
338
338
|
|
|
339
339
|
## Running on oam.js (optional)
|
|
340
340
|
|
|
341
|
-
[oam.js](https://oamjs.org) runs this server unmodified, and the launcher only runs the server on an oam that is **0.
|
|
341
|
+
[oam.js](https://oamjs.org) runs this server unmodified, and the launcher only runs the server on an oam that is **0.18.0 or newer**. 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.18.0 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.
|
|
342
342
|
|
|
343
|
-
**oam 0.
|
|
343
|
+
**oam 0.18.0 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.
|
|
344
344
|
|
|
345
345
|
The published `lemonsqueezy-mcp` command (`bin/lemonsqueezy-mcp.mjs`, which is what `npx` runs) chooses its runtime from these variables:
|
|
346
346
|
|
|
347
347
|
| Variable | Default | Purpose |
|
|
348
348
|
|----------|---------|---------|
|
|
349
|
-
| `LEMONSQUEEZY_MCP_RUNTIME` | `auto` | `auto`: serve on the oam the launcher is already running under if that is 0.
|
|
350
|
-
| `OAM_BIN` | unset | Path to an oam binary to use in preference to discovery, when it is 0.
|
|
349
|
+
| `LEMONSQUEEZY_MCP_RUNTIME` | `auto` | `auto`: serve on the oam the launcher is already running under if that is 0.18.0 or newer (unless `LEMONSQUEEZY_MCP_SANDBOX=1`); otherwise run on the newest oam binary it can find at 0.18.0 or newer (see `OAM_BIN`); otherwise on Node. An oam host older than 0.18.0 never serves the server itself -- it hands off to the newest usable oam, or to Node on `PATH`, or exits with an error when there is neither. On stderr, an `OAM_BIN` that was passed over is always named. The other oam binaries passed over, and any `oam.cmd` / `oam.bat` shim, are named only when no usable oam is found -- an older copy skipped for a newer one is not mentioned. A chosen oam that fails to launch, and a handoff to Node from an oam host older than 0.18.0, are always reported; when such a host hands off to a newer oam instead, nothing is printed. `oam`: the same, but exit with an error instead of falling back. `node`: always Node -- in-process under `npx`, and handed off to Node on `PATH` when a client launches the command with `oam run`. Case-insensitive, and any other value behaves like `auto`. |
|
|
350
|
+
| `OAM_BIN` | unset | Path to an oam binary to use in preference to discovery, when it is 0.18.0 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 the installed location (`%LOCALAPPDATA%\oam\bin` then `~/.oam/bin` on Windows, `~/.oam/bin` elsewhere) and on `PATH`, asks every oam it finds for its version, and uses the newest; on a tie the installed copy wins. On Windows only `oam.exe` counts: an `oam.cmd` / `oam.bat` shim is never run, and is named on stderr only when no usable oam is found. Ignored under `LEMONSQUEEZY_MCP_RUNTIME=node`, and when already running on oam 0.18.0+ without the sandbox. |
|
|
351
351
|
| `LEMONSQUEEZY_MCP_SANDBOX` | unset | `1` runs the server under `--permission`; see below. |
|
|
352
352
|
|
|
353
353
|
### Sandboxing (opt-in)
|
|
354
354
|
|
|
355
355
|
Set `LEMONSQUEEZY_MCP_SANDBOX=1` to run under oam's `--permission` model: network restricted to `api.lemonsqueezy.com` (plus the host of `LEMONSQUEEZY_SINK_URL` when set), filesystem denied outright, and child-process denied unless `LEMONSQUEEZY_API_KEY_COMMAND` is configured.
|
|
356
356
|
|
|
357
|
-
`--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.
|
|
357
|
+
`--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.18.0+. 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.
|
|
358
358
|
|
|
359
359
|
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.
|
|
360
360
|
|
|
@@ -401,7 +401,7 @@ It then runs eight steps on the workstation:
|
|
|
401
401
|
4. Turn `## [Unreleased]` in `CHANGELOG.md` into `## [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 tag `vX.Y.Z`, and push `main` with the tag
|
|
402
402
|
5. `npm publish --access public`
|
|
403
403
|
6. Create the GitHub release, with the notes taken from the `## [X.Y.Z]` section of `CHANGELOG.md`
|
|
404
|
-
7. Wait for npm to serve the new version (up to
|
|
404
|
+
7. Wait 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 publish `server.json` to the [Official MCP Registry](https://registry.modelcontextprotocol.io) with `mcp-publisher`
|
|
405
405
|
8. Verify the npm version, `package.json` and the tag
|
|
406
406
|
|
|
407
407
|
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.
|
|
@@ -418,7 +418,7 @@ One-time setup on each machine:
|
|
|
418
418
|
```
|
|
419
419
|
|
|
420
420
|
**Never run `npm login --auth-type=web`.** It overwrites the automation token with a 2FA-bound web session, and the next publish fails with `EOTP`.
|
|
421
|
-
- **`gh auth login`.** The pre-flight requires it, and step 6 creates the release with it. Step 7 logs `mcp-publisher` in with its token (`gh auth token`) unless `MCP_REGISTRY_TOKEN` is set; either way the token
|
|
421
|
+
- **`gh auth login`.** The pre-flight requires it, and step 6 creates the release with it. Step 7 logs `mcp-publisher` in with its token (`gh auth token`) unless `MCP_REGISTRY_TOKEN` is set; either way the registry grants the `io.github.YawLabs/*` namespace only to a YawLabs org Owner whose token can read org roles (`read:org` does; so do `repo`, `user`, `write:org` and `admin: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-publisher` is downloaded to `~/.local/bin` on first use; set `MCP_PUBLISHER` to use another copy.
|
|
422
422
|
- **`jq`, `curl` and `tar` on `PATH`.** Step 3 edits `server.json` with `jq`. Step 7 downloads `mcp-publisher` with `curl` and `tar` on first use, unless `MCP_PUBLISHER` points at an existing copy. It also polls npm with `curl`, and skips that wait with a warning if `curl` is missing.
|
|
423
423
|
- **Rights to push to `main` and create `v*` tags.** The repository rulesets require a pull request for `main` and restrict `v*` tag creation, only organization admins bypass them, and step 4 pushes both directly.
|
|
424
424
|
|
|
@@ -426,4 +426,4 @@ One-time setup on each machine:
|
|
|
426
426
|
|
|
427
427
|
MIT
|
|
428
428
|
|
|
429
|
-
[](https://x.com/YawLabs)
|
package/bin/lemonsqueezy-mcp.mjs
CHANGED
|
@@ -96,7 +96,7 @@
|
|
|
96
96
|
* shipped bundle actually reads -- keep it in step with the bundle.
|
|
97
97
|
*
|
|
98
98
|
* MINIMUM OAM VERSION
|
|
99
|
-
* OAM_MIN, 0.
|
|
99
|
+
* OAM_MIN, 0.18.0, is a floor: the oam this server was last verified on, not
|
|
100
100
|
* a claim that it is the newest oam. Raise it once a newer oam has been
|
|
101
101
|
* re-verified here (handshake and all 64 tools through this launcher). The
|
|
102
102
|
* server only runs on an oam at or above the floor; an older one is never
|
|
@@ -128,7 +128,7 @@ import { delimiter, join } from "node:path";
|
|
|
128
128
|
import { fileURLToPath } from "node:url";
|
|
129
129
|
|
|
130
130
|
/** Oldest oam this server is served on. See MINIMUM OAM VERSION above. */
|
|
131
|
-
const OAM_MIN = [0,
|
|
131
|
+
const OAM_MIN = [0, 18, 0];
|
|
132
132
|
|
|
133
133
|
/**
|
|
134
134
|
* Bound on each `oam --version` probe. A healthy oam answers in milliseconds;
|
package/dist/index.js
CHANGED
|
@@ -4148,13 +4148,14 @@ var require_fast_uri = __commonJS({
|
|
|
4148
4148
|
if (!malformedIPLiteral) {
|
|
4149
4149
|
malformedHost = canonicalizeHost(parsed, options, schemeHandler, isIP);
|
|
4150
4150
|
}
|
|
4151
|
-
if (
|
|
4152
|
-
|
|
4153
|
-
|
|
4154
|
-
|
|
4155
|
-
parsed.host = reescapeHostDelimiters(host, isIP);
|
|
4156
|
-
}
|
|
4151
|
+
if (uri.indexOf("%") !== -1 && parsed.host !== void 0 && !malformedIPLiteral) {
|
|
4152
|
+
let host = isIP ? parsed.host : normalizePercentEncoding(parsed.host, true);
|
|
4153
|
+
if (!isIP) {
|
|
4154
|
+
host = normalizePercentEncoding(host.toLowerCase());
|
|
4157
4155
|
}
|
|
4156
|
+
parsed.host = reescapeHostDelimiters(host, isIP);
|
|
4157
|
+
}
|
|
4158
|
+
if (!schemeHandler || schemeHandler && !schemeHandler.skipNormalize) {
|
|
4158
4159
|
if (parsed.path) {
|
|
4159
4160
|
parsed.path = normalizePathEncoding(parsed.path);
|
|
4160
4161
|
}
|
|
@@ -28700,6 +28701,9 @@ var Protocol = class {
|
|
|
28700
28701
|
this.setRequestHandler(GetTaskPayloadRequestSchema, async (request, extra) => {
|
|
28701
28702
|
const handleTaskResult = async () => {
|
|
28702
28703
|
const taskId = request.params.taskId;
|
|
28704
|
+
if (!await this._taskStore.getTask(taskId, extra.sessionId)) {
|
|
28705
|
+
throw new McpError(ErrorCode.InvalidParams, `Task not found: ${taskId}`);
|
|
28706
|
+
}
|
|
28703
28707
|
if (this._taskMessageQueue) {
|
|
28704
28708
|
let queuedMessage;
|
|
28705
28709
|
while (queuedMessage = await this._taskMessageQueue.dequeue(taskId, extra.sessionId)) {
|
|
@@ -28730,12 +28734,12 @@ var Protocol = class {
|
|
|
28730
28734
|
throw new McpError(ErrorCode.InvalidParams, `Task not found: ${taskId}`);
|
|
28731
28735
|
}
|
|
28732
28736
|
if (!isTerminal(task.status)) {
|
|
28733
|
-
await this._waitForTaskUpdate(taskId, extra.signal);
|
|
28737
|
+
await this._waitForTaskUpdate(taskId, extra.signal, extra.sessionId);
|
|
28734
28738
|
return await handleTaskResult();
|
|
28735
28739
|
}
|
|
28736
28740
|
if (isTerminal(task.status)) {
|
|
28737
28741
|
const result = await this._taskStore.getTaskResult(taskId, extra.sessionId);
|
|
28738
|
-
this._clearTaskQueue(taskId);
|
|
28742
|
+
this._clearTaskQueue(taskId, extra.sessionId);
|
|
28739
28743
|
return {
|
|
28740
28744
|
...result,
|
|
28741
28745
|
_meta: {
|
|
@@ -28772,7 +28776,7 @@ var Protocol = class {
|
|
|
28772
28776
|
throw new McpError(ErrorCode.InvalidParams, `Cannot cancel task in terminal status: ${task.status}`);
|
|
28773
28777
|
}
|
|
28774
28778
|
await this._taskStore.updateTaskStatus(request.params.taskId, "cancelled", "Client cancelled task execution.", extra.sessionId);
|
|
28775
|
-
this._clearTaskQueue(request.params.taskId);
|
|
28779
|
+
this._clearTaskQueue(request.params.taskId, extra.sessionId);
|
|
28776
28780
|
const cancelledTask = await this._taskStore.getTask(request.params.taskId, extra.sessionId);
|
|
28777
28781
|
if (!cancelledTask) {
|
|
28778
28782
|
throw new McpError(ErrorCode.InvalidParams, `Task not found after cancellation: ${request.params.taskId}`);
|
|
@@ -28900,6 +28904,19 @@ var Protocol = class {
|
|
|
28900
28904
|
const handler = this._requestHandlers.get(request.method) ?? this.fallbackRequestHandler;
|
|
28901
28905
|
const capturedTransport = this._transport;
|
|
28902
28906
|
const relatedTaskId = request.params?._meta?.[RELATED_TASK_META_KEY]?.taskId;
|
|
28907
|
+
const sessionId = capturedTransport?.sessionId;
|
|
28908
|
+
const store = this._taskStore;
|
|
28909
|
+
let relatedTaskFound = true;
|
|
28910
|
+
let relatedTaskLookup;
|
|
28911
|
+
if (relatedTaskId && store && this._taskMessageQueue && sessionId !== void 0) {
|
|
28912
|
+
relatedTaskFound = false;
|
|
28913
|
+
relatedTaskLookup = (async () => {
|
|
28914
|
+
if (!await store.getTask(relatedTaskId, sessionId)) {
|
|
28915
|
+
throw new McpError(ErrorCode.InvalidParams, `Task not found: ${relatedTaskId}`);
|
|
28916
|
+
}
|
|
28917
|
+
relatedTaskFound = true;
|
|
28918
|
+
})();
|
|
28919
|
+
}
|
|
28903
28920
|
if (handler === void 0) {
|
|
28904
28921
|
const errorResponse = {
|
|
28905
28922
|
jsonrpc: "2.0",
|
|
@@ -28909,7 +28926,10 @@ var Protocol = class {
|
|
|
28909
28926
|
message: "Method not found"
|
|
28910
28927
|
}
|
|
28911
28928
|
};
|
|
28912
|
-
if (relatedTaskId &&
|
|
28929
|
+
if (relatedTaskId && relatedTaskLookup) {
|
|
28930
|
+
const queuedError = { type: "error", message: errorResponse, timestamp: Date.now() };
|
|
28931
|
+
relatedTaskLookup.then(() => this._enqueueTaskMessage(relatedTaskId, queuedError, sessionId), () => capturedTransport?.send(errorResponse)).catch((error51) => this._onerror(new Error(`Failed to send an error response: ${error51}`)));
|
|
28932
|
+
} else if (relatedTaskId && this._taskMessageQueue) {
|
|
28913
28933
|
this._enqueueTaskMessage(relatedTaskId, {
|
|
28914
28934
|
type: "error",
|
|
28915
28935
|
message: errorResponse,
|
|
@@ -28960,7 +28980,10 @@ var Protocol = class {
|
|
|
28960
28980
|
closeSSEStream: extra?.closeSSEStream,
|
|
28961
28981
|
closeStandaloneSSEStream: extra?.closeStandaloneSSEStream
|
|
28962
28982
|
};
|
|
28963
|
-
Promise.resolve().then(() => {
|
|
28983
|
+
(relatedTaskLookup ?? Promise.resolve()).then(() => {
|
|
28984
|
+
if (relatedTaskLookup && abortController.signal.aborted) {
|
|
28985
|
+
throw new McpError(ErrorCode.ConnectionClosed, "Request was cancelled");
|
|
28986
|
+
}
|
|
28964
28987
|
if (taskCreationParams) {
|
|
28965
28988
|
this.assertTaskHandlerCapability(request.method);
|
|
28966
28989
|
}
|
|
@@ -28995,7 +29018,7 @@ var Protocol = class {
|
|
|
28995
29018
|
...error51["data"] !== void 0 && { data: error51["data"] }
|
|
28996
29019
|
}
|
|
28997
29020
|
};
|
|
28998
|
-
if (relatedTaskId && this._taskMessageQueue) {
|
|
29021
|
+
if (relatedTaskId && this._taskMessageQueue && relatedTaskFound) {
|
|
28999
29022
|
await this._enqueueTaskMessage(relatedTaskId, {
|
|
29000
29023
|
type: "error",
|
|
29001
29024
|
message: errorResponse,
|
|
@@ -29475,7 +29498,7 @@ var Protocol = class {
|
|
|
29475
29498
|
throw new Error("Cannot enqueue task message: taskStore and taskMessageQueue are not configured");
|
|
29476
29499
|
}
|
|
29477
29500
|
const maxQueueSize = this._options?.maxTaskQueueSize;
|
|
29478
|
-
await this._taskMessageQueue.enqueue(taskId, message, sessionId, maxQueueSize);
|
|
29501
|
+
await this._taskMessageQueue.enqueue(taskId, message, sessionId ?? this._transport?.sessionId, maxQueueSize);
|
|
29479
29502
|
}
|
|
29480
29503
|
/**
|
|
29481
29504
|
* Clears the message queue for a task and rejects any pending request resolvers.
|
|
@@ -29504,12 +29527,13 @@ var Protocol = class {
|
|
|
29504
29527
|
* Uses polling to check for updates at the task's configured poll interval.
|
|
29505
29528
|
* @param taskId The task ID to wait for
|
|
29506
29529
|
* @param signal Abort signal to cancel the wait
|
|
29530
|
+
* @param sessionId Session of the request that waits, passed to the task store
|
|
29507
29531
|
* @returns Promise that resolves when an update occurs or rejects if aborted
|
|
29508
29532
|
*/
|
|
29509
|
-
async _waitForTaskUpdate(taskId, signal) {
|
|
29533
|
+
async _waitForTaskUpdate(taskId, signal, sessionId) {
|
|
29510
29534
|
let interval = this._options?.defaultTaskPollInterval ?? 1e3;
|
|
29511
29535
|
try {
|
|
29512
|
-
const task = await this._taskStore?.getTask(taskId);
|
|
29536
|
+
const task = await this._taskStore?.getTask(taskId, sessionId);
|
|
29513
29537
|
if (task?.pollInterval) {
|
|
29514
29538
|
interval = task.pollInterval;
|
|
29515
29539
|
}
|
|
@@ -30388,6 +30412,42 @@ var ExperimentalMcpServerTasks = class {
|
|
|
30388
30412
|
};
|
|
30389
30413
|
|
|
30390
30414
|
// node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js
|
|
30415
|
+
function toolInputElementCount(value, max) {
|
|
30416
|
+
let count = 0;
|
|
30417
|
+
const stack = [value];
|
|
30418
|
+
while (stack.length > 0) {
|
|
30419
|
+
const node = stack.pop();
|
|
30420
|
+
if (node === null || typeof node !== "object")
|
|
30421
|
+
continue;
|
|
30422
|
+
if (Array.isArray(node)) {
|
|
30423
|
+
for (const child of node) {
|
|
30424
|
+
if (++count > max)
|
|
30425
|
+
return count;
|
|
30426
|
+
if (child !== null && typeof child === "object")
|
|
30427
|
+
stack.push(child);
|
|
30428
|
+
}
|
|
30429
|
+
} else {
|
|
30430
|
+
for (const key in node) {
|
|
30431
|
+
if (!Object.prototype.hasOwnProperty.call(node, key))
|
|
30432
|
+
continue;
|
|
30433
|
+
if (++count > max)
|
|
30434
|
+
return count;
|
|
30435
|
+
const child = node[key];
|
|
30436
|
+
if (child !== null && typeof child === "object")
|
|
30437
|
+
stack.push(child);
|
|
30438
|
+
}
|
|
30439
|
+
}
|
|
30440
|
+
}
|
|
30441
|
+
return count;
|
|
30442
|
+
}
|
|
30443
|
+
function resolveMaxToolInputElements(value) {
|
|
30444
|
+
if (value === void 0 || value === Infinity)
|
|
30445
|
+
return void 0;
|
|
30446
|
+
if (typeof value !== "number" || Number.isNaN(value) || value < 1) {
|
|
30447
|
+
throw new RangeError(`maxToolInputElements must be a number of at least 1, or Infinity, got ${String(value)}`);
|
|
30448
|
+
}
|
|
30449
|
+
return value;
|
|
30450
|
+
}
|
|
30391
30451
|
var McpServer = class {
|
|
30392
30452
|
constructor(serverInfo, options) {
|
|
30393
30453
|
this._registeredResources = {};
|
|
@@ -30399,6 +30459,7 @@ var McpServer = class {
|
|
|
30399
30459
|
this._resourceHandlersInitialized = false;
|
|
30400
30460
|
this._promptHandlersInitialized = false;
|
|
30401
30461
|
this.server = new Server(serverInfo, options);
|
|
30462
|
+
this._maxToolInputElements = resolveMaxToolInputElements(options?.maxToolInputElements);
|
|
30402
30463
|
}
|
|
30403
30464
|
/**
|
|
30404
30465
|
* Access experimental features.
|
|
@@ -30529,12 +30590,15 @@ var McpServer = class {
|
|
|
30529
30590
|
* Validates tool input arguments against the tool's input schema.
|
|
30530
30591
|
*/
|
|
30531
30592
|
async validateToolInput(tool, args, toolName) {
|
|
30593
|
+
if (this._maxToolInputElements !== void 0 && toolInputElementCount(args, this._maxToolInputElements) > this._maxToolInputElements) {
|
|
30594
|
+
throw new McpError(ErrorCode.InvalidParams, `Invalid arguments for tool ${toolName}: arguments contain more than the maximum of ${this._maxToolInputElements} elements`);
|
|
30595
|
+
}
|
|
30532
30596
|
if (!tool.inputSchema) {
|
|
30533
30597
|
return void 0;
|
|
30534
30598
|
}
|
|
30535
30599
|
const inputObj = normalizeObjectSchema(tool.inputSchema);
|
|
30536
30600
|
const schemaToParse = inputObj ?? tool.inputSchema;
|
|
30537
|
-
const parseResult = await safeParseAsync2(schemaToParse, args);
|
|
30601
|
+
const parseResult = await safeParseAsync2(schemaToParse, args ?? {});
|
|
30538
30602
|
if (!parseResult.success) {
|
|
30539
30603
|
const error51 = "error" in parseResult ? parseResult.error : "Unknown error";
|
|
30540
30604
|
const errorMessage = getParseErrorMessage(error51);
|
|
@@ -30772,7 +30836,7 @@ var McpServer = class {
|
|
|
30772
30836
|
}
|
|
30773
30837
|
if (prompt.argsSchema) {
|
|
30774
30838
|
const argsObj = normalizeObjectSchema(prompt.argsSchema);
|
|
30775
|
-
const parseResult = await safeParseAsync2(argsObj, request.params.arguments);
|
|
30839
|
+
const parseResult = await safeParseAsync2(argsObj, request.params.arguments ?? {});
|
|
30776
30840
|
if (!parseResult.success) {
|
|
30777
30841
|
const error51 = "error" in parseResult ? parseResult.error : "Unknown error";
|
|
30778
30842
|
const errorMessage = getParseErrorMessage(error51);
|
|
@@ -34160,7 +34224,7 @@ function readAuditLogResource(uri) {
|
|
|
34160
34224
|
}
|
|
34161
34225
|
|
|
34162
34226
|
// src/index.ts
|
|
34163
|
-
var version2 = true ? "1.0.
|
|
34227
|
+
var version2 = true ? "1.0.2" : createRequire(import.meta.url)("../package.json").version;
|
|
34164
34228
|
var subcommand = process.argv[2];
|
|
34165
34229
|
if (subcommand === "version" || subcommand === "--version") {
|
|
34166
34230
|
console.log(version2);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yawlabs/lemonsqueezy-mcp",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"mcpName": "io.github.YawLabs/lemonsqueezy-mcp",
|
|
5
5
|
"description": "Lemon Squeezy MCP server: stores, orders, subscriptions, license keys, refunds, and webhooks.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
},
|
|
64
64
|
"devDependencies": {
|
|
65
65
|
"@biomejs/biome": "^2.4.12",
|
|
66
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
66
|
+
"@modelcontextprotocol/sdk": "^1.32.1",
|
|
67
67
|
"@types/node": "^26.1.1",
|
|
68
68
|
"esbuild": "^0.28.1",
|
|
69
69
|
"postject": "^1.0.0-alpha.6",
|