@thenavidm/slipway 0.1.0 → 0.1.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 +9 -0
- package/README.md +6 -3
- package/SKILL.md +3 -3
- package/dist/bin.js +7 -1
- package/dist/check.d.ts +2 -0
- package/dist/check.js +38 -0
- package/dist/install.d.ts +2 -1
- package/dist/install.js +3 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
What changed in Slipway, newest first.
|
|
4
4
|
|
|
5
|
+
## 0.1.2, 2026-10-04: clients always start the server
|
|
6
|
+
|
|
7
|
+
- **`slipway check` fails a package whose `npx -y` default is the CLI.** With several binaries on one file, npx starts the first one listed. A package that lists its CLI first hands every client launched with `npx -y <package>` the command list instead of a server. The check reads `package.json` and names the fix: list the MCP binary first.
|
|
8
|
+
- **`install` follows `@latest`.** Clients start `npx --package=<package>@latest <name>-mcp`, so they pick up every release on their next start. The binary is still named, so the order in `package.json` cannot pick the wrong one.
|
|
9
|
+
|
|
10
|
+
## 0.1.1, 2026-10-04: a safer install check
|
|
11
|
+
|
|
12
|
+
- **No stranger's package through npx.** An unrelated npm package owns the bare name `slipway`, so a bare `npx slipway` with nothing installed fetched and ran it. SKILL.md now checks the install with `npm ls @thenavidm/slipway`, and every command that may run before an install names the package: `npx -p @thenavidm/slipway slipway <command>`.
|
|
13
|
+
|
|
5
14
|
## 0.1.0, 2026-10-04: the first release
|
|
6
15
|
|
|
7
16
|
- **One definition, two surfaces.** `defineTool` describes a tool once; `slipway()` ships it as an MCP server tool and a CLI command under the same name. Both surfaces send every call through one function, so validation, the write guard, timeouts, cancellation and redaction cannot differ between them.
|
package/README.md
CHANGED
|
@@ -444,7 +444,7 @@ notes-cli install cursor --dry-run
|
|
|
444
444
|
| `vscode` | `.vscode/mcp.json` | VS Code asks for each credential once and stores it securely |
|
|
445
445
|
| `gemini` | `~/.gemini/settings.json`, or `.gemini/settings.json` | `${NAME}` references, which Gemini CLI needs to pass anything named like a key |
|
|
446
446
|
|
|
447
|
-
A published server is started with `npx --package=<package
|
|
447
|
+
A published server is started with `npx --package=<package>@latest <name>-mcp`, so a client picks up every release on its next start, with Codex's startup timeout raised for the download. The binary is named, because npx alone starts whichever binary a package lists first. Without `package`, or with `--local`, the client starts this copy on disk. Installing again updates the entry in place: anything you added to it by hand stays, and the old file is kept as a backup.
|
|
448
448
|
|
|
449
449
|
## 11. Large catalogs
|
|
450
450
|
|
|
@@ -459,9 +459,11 @@ A server with a hundred tools costs a client that loads every definition up fron
|
|
|
459
459
|
`slipway check` runs against your built app and its real MCP server:
|
|
460
460
|
|
|
461
461
|
```bash
|
|
462
|
-
slipway check dist/app.js --bin dist/index.js --docs README.md,SKILL.md
|
|
462
|
+
npx slipway check dist/app.js --bin dist/index.js --docs README.md,SKILL.md
|
|
463
463
|
```
|
|
464
464
|
|
|
465
|
+
Run it in a project that has `@thenavidm/slipway` installed, where npx uses that copy. Anywhere else, name the package: `npx -p @thenavidm/slipway slipway openapi spec.json`. A bare `npx slipway` with nothing installed fetches an unrelated npm package of the same name.
|
|
466
|
+
|
|
465
467
|
| Check | What fails |
|
|
466
468
|
|---|---|
|
|
467
469
|
| Names | A tool that takes a built-in command's name |
|
|
@@ -474,7 +476,7 @@ slipway check dist/app.js --bin dist/index.js --docs README.md,SKILL.md
|
|
|
474
476
|
| Parity | A tool, schema, annotation or approval flag that differs between MCP and the CLI |
|
|
475
477
|
| Docs | A command or flag in your README or SKILL.md that does not exist |
|
|
476
478
|
| Startup | A built server that exits or hangs when nothing is configured |
|
|
477
|
-
| Install | No `package`,
|
|
479
|
+
| Install | No `package`, or a package.json whose `npx -y` default starts the CLI instead of the server |
|
|
478
480
|
|
|
479
481
|
Parity runs on both protocol revisions a client may open with. `slipway docs dist/app.js` prints the command table, every argument and the settings as Markdown, from the same definitions. `slipway inspect dist/app.js` lists the tools exactly as a client receives them, and `slipway openapi <file|url>` previews what an OpenAPI document becomes.
|
|
480
482
|
|
|
@@ -519,6 +521,7 @@ const mcp = await connect(app, { era: "modern", elicit: () => ({ action: "accept
|
|
|
519
521
|
| Codex shows the server as failed at startup | The first npx download outlasted 10 seconds | `install codex` sets `startup_timeout_sec = 60`; add it by hand to an older entry |
|
|
520
522
|
| `slipway check` warns about schema size | One tool's schema is large or repeats its definitions | Send the body schema once, or advertise a short one and validate the full one in the handler |
|
|
521
523
|
| `slipway check` cannot load the app | The module starts the server when imported | Export the app from `app.ts` and call `app.main()` only in `index.ts` |
|
|
524
|
+
| `npx slipway` prints something unexpected | Slipway is not installed in this folder, so npx fetched an unrelated package called `slipway` | Run `npm install @thenavidm/slipway`, or `npx -p @thenavidm/slipway slipway <command>` |
|
|
522
525
|
|
|
523
526
|
## Environment variables
|
|
524
527
|
|
package/SKILL.md
CHANGED
|
@@ -5,7 +5,7 @@ metadata:
|
|
|
5
5
|
install:
|
|
6
6
|
package: "@thenavidm/slipway"
|
|
7
7
|
node: ">=22"
|
|
8
|
-
check: "
|
|
8
|
+
check: "npm ls @thenavidm/slipway"
|
|
9
9
|
---
|
|
10
10
|
|
|
11
11
|
# Building with Slipway
|
|
@@ -14,7 +14,7 @@ Slipway turns one list of tool definitions into an MCP server and a CLI. Both su
|
|
|
14
14
|
|
|
15
15
|
## Install gate
|
|
16
16
|
|
|
17
|
-
Run `
|
|
17
|
+
Run `npm ls @thenavidm/slipway` in the repo. If it does not list a version, STOP: the package is missing. Install it with `npm install @thenavidm/slipway`, then check again. Never test with a bare `npx slipway` before it is installed: an unrelated package on npm is called `slipway`, and npx would fetch and run that one.
|
|
18
18
|
|
|
19
19
|
## The three files
|
|
20
20
|
|
|
@@ -93,7 +93,7 @@ tools: fromOpenAPI(spec, {
|
|
|
93
93
|
}),
|
|
94
94
|
```
|
|
95
95
|
|
|
96
|
-
Run `npx slipway openapi openapi.json` first: it lists every tool the document becomes, what it skips and why, the schemas too large for a model, and the hash to pin. Fix a misleading method with `risk: { searchProducts: "read" }`, and rename with `names`.
|
|
96
|
+
Run `npx -p @thenavidm/slipway slipway openapi openapi.json` first: it lists every tool the document becomes, what it skips and why, the schemas too large for a model, and the hash to pin. Fix a misleading method with `risk: { searchProducts: "read" }`, and rename with `names`.
|
|
97
97
|
|
|
98
98
|
## Shipping it to clients
|
|
99
99
|
|
package/dist/bin.js
CHANGED
|
@@ -95,7 +95,13 @@ async function main(argv) {
|
|
|
95
95
|
if (command === "check") {
|
|
96
96
|
const docs = option(rest, "--docs")?.split(",").filter(Boolean);
|
|
97
97
|
const bin = option(rest, "--bin");
|
|
98
|
-
const
|
|
98
|
+
const packageJson = resolve("package.json");
|
|
99
|
+
const report = await checkApp(app, {
|
|
100
|
+
env: process.env,
|
|
101
|
+
...(docs ? { docs } : {}),
|
|
102
|
+
...(bin ? { bin: resolve(bin) } : {}),
|
|
103
|
+
...(existsSync(packageJson) ? { packageJson } : {}),
|
|
104
|
+
});
|
|
99
105
|
const strict = rest.includes("--strict");
|
|
100
106
|
const failed = report.errors > 0 || (strict && report.warnings > 0);
|
|
101
107
|
if (rest.includes("--json")) {
|
package/dist/check.d.ts
CHANGED
package/dist/check.js
CHANGED
|
@@ -108,6 +108,8 @@ export async function checkApp(app, options = {}) {
|
|
|
108
108
|
if (!app.definition.package) {
|
|
109
109
|
add("warn", "install", "No package is set, so `install` points clients at this copy on disk instead of the published one. Set package to the npm name.");
|
|
110
110
|
}
|
|
111
|
+
if (options.packageJson)
|
|
112
|
+
checkBins(app, options.packageJson, add);
|
|
111
113
|
const instructions = app.instructions ?? "";
|
|
112
114
|
if (!instructions)
|
|
113
115
|
add("warn", "instructions", "No server instructions. Clients use them to decide when to reach for these tools.");
|
|
@@ -215,6 +217,42 @@ async function checkParityIn(era, app, env, tools, add) {
|
|
|
215
217
|
await client.close().catch(() => undefined);
|
|
216
218
|
}
|
|
217
219
|
}
|
|
220
|
+
/**
|
|
221
|
+
* `npx -y <package>` is how most people install a server, and npx starts one
|
|
222
|
+
* binary without being told which. With several binaries on one file it runs
|
|
223
|
+
* the first listed, so a package that lists its CLI first hands every client
|
|
224
|
+
* the command list instead of a server.
|
|
225
|
+
*/
|
|
226
|
+
function checkBins(app, file, add) {
|
|
227
|
+
let pkg;
|
|
228
|
+
try {
|
|
229
|
+
pkg = JSON.parse(readFileSync(file, "utf8"));
|
|
230
|
+
}
|
|
231
|
+
catch (error) {
|
|
232
|
+
add("error", "install", `Could not read ${file}: ${error.message}`);
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
if (app.definition.package && pkg.name && pkg.name !== app.definition.package) {
|
|
236
|
+
add("warn", "install", `The app's package is ${app.definition.package} but package.json is ${pkg.name}.`);
|
|
237
|
+
}
|
|
238
|
+
if (!pkg.bin || typeof pkg.bin === "string")
|
|
239
|
+
return;
|
|
240
|
+
const bins = pkg.bin;
|
|
241
|
+
const keys = Object.keys(bins);
|
|
242
|
+
if (!keys.includes(app.bins.mcp)) {
|
|
243
|
+
add("error", "install", `package.json has no ${app.bins.mcp} binary, so clients cannot start the server by name.`);
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
const unscoped = (pkg.name ?? "").split("/").pop() ?? "";
|
|
247
|
+
// npx's own order: a binary named after the package, then the only file all binaries share.
|
|
248
|
+
const chosen = keys.includes(unscoped) ? unscoped : new Set(Object.values(bins)).size === 1 ? keys[0] : undefined;
|
|
249
|
+
if (chosen === undefined) {
|
|
250
|
+
add("error", "install", `npx -y ${pkg.name} cannot choose between ${keys.join(" and ")}. Point them at one file and list ${app.bins.mcp} first.`);
|
|
251
|
+
}
|
|
252
|
+
else if (chosen !== app.bins.mcp && bins[chosen] === bins[app.bins.mcp] && chosen === app.bins.cli) {
|
|
253
|
+
add("error", "install", `npx -y ${pkg.name} starts ${chosen}, the CLI, so a client launched that way gets the command list instead of a server. List ${app.bins.mcp} first in package.json's bin.`);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
218
256
|
/** Every command and flag a README or SKILL.md tells someone to type must exist. */
|
|
219
257
|
function checkDocs(app, file, add) {
|
|
220
258
|
if (!existsSync(file)) {
|
package/dist/install.d.ts
CHANGED
|
@@ -59,7 +59,8 @@ export type InstallPlan = {
|
|
|
59
59
|
};
|
|
60
60
|
/**
|
|
61
61
|
* How a client should start this server: the published package through npx,
|
|
62
|
-
*
|
|
62
|
+
* at `@latest` so a client picks up every release on its next start, or this
|
|
63
|
+
* copy on disk.
|
|
63
64
|
*
|
|
64
65
|
* npx needs the binary named: a package with an MCP and a CLI binary leaves
|
|
65
66
|
* npx to pick one otherwise, and the CLI started with no arguments prints its
|
package/dist/install.js
CHANGED
|
@@ -27,7 +27,8 @@ export const CLIENTS = {
|
|
|
27
27
|
};
|
|
28
28
|
/**
|
|
29
29
|
* How a client should start this server: the published package through npx,
|
|
30
|
-
*
|
|
30
|
+
* at `@latest` so a client picks up every release on its next start, or this
|
|
31
|
+
* copy on disk.
|
|
31
32
|
*
|
|
32
33
|
* npx needs the binary named: a package with an MCP and a CLI binary leaves
|
|
33
34
|
* npx to pick one otherwise, and the CLI started with no arguments prints its
|
|
@@ -37,7 +38,7 @@ export function launchFor(app, options) {
|
|
|
37
38
|
const platform = options.platform ?? process.platform;
|
|
38
39
|
let launch;
|
|
39
40
|
if (app.definition.package && !options.local) {
|
|
40
|
-
launch = { command: "npx", args: ["--yes", `--package=${app.definition.package}
|
|
41
|
+
launch = { command: "npx", args: ["--yes", `--package=${app.definition.package}@latest`, app.bins.mcp] };
|
|
41
42
|
}
|
|
42
43
|
else {
|
|
43
44
|
const entry = options.entry ?? (process.argv[1] ? realpathSync(process.argv[1]) : undefined);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thenavidm/slipway",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Slipway, the TypeScript framework for MCP servers and agent-native CLIs. One tool definition ships an MCP server and a CLI, with write safety, typed results and release checks built in.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|