@thenavidm/slipway 0.1.1 → 0.1.3
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 +13 -4
- package/SKILL.md +2 -1
- package/dist/bin.js +7 -1
- package/dist/check.d.ts +2 -0
- package/dist/check.js +40 -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.3, 2026-10-04: npx picks the server by name
|
|
6
|
+
|
|
7
|
+
- **`slipway check` matches npm's real rule.** npx picks a binary named after the package only when the binaries point to different files. When they share one file it starts whichever one the registry lists first, and the registry does not keep the published order: 23 published servers listed their MCP binary first and still started the CLI. 0.1.2's order check could not catch that. The check now requires a binary named after the package on a file of its own, and the README shows the one-line `src/npx.ts` it runs.
|
|
8
|
+
|
|
9
|
+
## 0.1.2, 2026-10-04: clients always start the server
|
|
10
|
+
|
|
11
|
+
- **`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.
|
|
12
|
+
- **`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.
|
|
13
|
+
|
|
5
14
|
## 0.1.1, 2026-10-04: a safer install check
|
|
6
15
|
|
|
7
16
|
- **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>`.
|
package/README.md
CHANGED
|
@@ -124,7 +124,7 @@ npm install --save-dev ajv
|
|
|
124
124
|
|
|
125
125
|
## 2. Build a server
|
|
126
126
|
|
|
127
|
-
A server is three files.
|
|
127
|
+
A server is three files, plus a one-line fourth for npx.
|
|
128
128
|
|
|
129
129
|
**`src/tools.ts`** says what the server can do. `toolkit<Context>()` binds the context type once, so every handler gets `ctx.api` typed:
|
|
130
130
|
|
|
@@ -179,12 +179,21 @@ import { app } from "./app.js";
|
|
|
179
179
|
await app.main();
|
|
180
180
|
```
|
|
181
181
|
|
|
182
|
+
**`src/npx.ts`** is what `npx -y @you/notes-mcp-cli` runs:
|
|
183
|
+
|
|
184
|
+
```ts
|
|
185
|
+
#!/usr/bin/env node
|
|
186
|
+
import "./index.js";
|
|
187
|
+
```
|
|
188
|
+
|
|
182
189
|
```json
|
|
183
190
|
{
|
|
184
|
-
"bin": { "notes-mcp": "dist/index.js", "notes-cli": "dist/index.js" }
|
|
191
|
+
"bin": { "notes-mcp": "dist/index.js", "notes-cli": "dist/index.js", "notes-mcp-cli": "dist/npx.js" }
|
|
185
192
|
}
|
|
186
193
|
```
|
|
187
194
|
|
|
195
|
+
npx picks a binary named after the package only when the binaries point to different files. When they all share one file it starts whichever one the registry lists first, and the registry does not keep the order they were published in, so a client could get the CLI's command list instead of a server. The fourth binary, on its own file, is picked every time, and `slipway check` fails a package without it.
|
|
196
|
+
|
|
188
197
|
`notes-mcp` with no arguments serves MCP over stdio and stays silent on stdout. `notes-cli` with no arguments lists the commands. Any argument on either binary is a command, so a typo is reported instead of starting a server that waits on stdin.
|
|
189
198
|
|
|
190
199
|
The context is built on the first call that needs it, never at startup. `--help` works with nothing configured, and the server answers a client at once and explains what is missing instead of exiting.
|
|
@@ -444,7 +453,7 @@ notes-cli install cursor --dry-run
|
|
|
444
453
|
| `vscode` | `.vscode/mcp.json` | VS Code asks for each credential once and stores it securely |
|
|
445
454
|
| `gemini` | `~/.gemini/settings.json`, or `.gemini/settings.json` | `${NAME}` references, which Gemini CLI needs to pass anything named like a key |
|
|
446
455
|
|
|
447
|
-
A published server is started with `npx --package=<package
|
|
456
|
+
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
457
|
|
|
449
458
|
## 11. Large catalogs
|
|
450
459
|
|
|
@@ -476,7 +485,7 @@ Run it in a project that has `@thenavidm/slipway` installed, where npx uses that
|
|
|
476
485
|
| Parity | A tool, schema, annotation or approval flag that differs between MCP and the CLI |
|
|
477
486
|
| Docs | A command or flag in your README or SKILL.md that does not exist |
|
|
478
487
|
| Startup | A built server that exits or hangs when nothing is configured |
|
|
479
|
-
| Install | No `package`,
|
|
488
|
+
| Install | No `package`, or a package.json whose `npx -y` default starts the CLI instead of the server |
|
|
480
489
|
|
|
481
490
|
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.
|
|
482
491
|
|
package/SKILL.md
CHANGED
|
@@ -23,8 +23,9 @@ Run `npm ls @thenavidm/slipway` in the repo. If it does not list a version, STOP
|
|
|
23
23
|
| `src/tools.ts` | The tools, from `toolkit<Context>().defineTool` | One `defineTool` per action |
|
|
24
24
|
| `src/app.ts` | `export const app = slipway({...})` | Describes only. Never calls `main()`, so checks and tests can import it |
|
|
25
25
|
| `src/index.ts` | `await app.main()` | The only file that starts anything. Both binaries point at it |
|
|
26
|
+
| `src/npx.ts` | `import "./index.js";` | What `npx -y <package>` runs. Its binary is named after the package |
|
|
26
27
|
|
|
27
|
-
`package.json` declares
|
|
28
|
+
`package.json` declares `"<name>-mcp"` and `"<name>-cli"` on `dist/index.js`, and a third binary named after the package (`"<name>-mcp-cli"`) on `dist/npx.js`. npx only picks a binary by name when they point to different files; otherwise it takes whichever one the registry lists first, which may be the CLI.
|
|
28
29
|
|
|
29
30
|
## Defining a tool
|
|
30
31
|
|
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,44 @@ 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. npm's rule: when every binary points to
|
|
223
|
+
* the same file, it starts whichever one the registry lists first, and the
|
|
224
|
+
* registry does not keep the order they were published in. Only a binary
|
|
225
|
+
* named after the package, on a file of its own, is picked every time.
|
|
226
|
+
*/
|
|
227
|
+
function checkBins(app, file, add) {
|
|
228
|
+
let pkg;
|
|
229
|
+
try {
|
|
230
|
+
pkg = JSON.parse(readFileSync(file, "utf8"));
|
|
231
|
+
}
|
|
232
|
+
catch (error) {
|
|
233
|
+
add("error", "install", `Could not read ${file}: ${error.message}`);
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
if (app.definition.package && pkg.name && pkg.name !== app.definition.package) {
|
|
237
|
+
add("warn", "install", `The app's package is ${app.definition.package} but package.json is ${pkg.name}.`);
|
|
238
|
+
}
|
|
239
|
+
if (!pkg.bin || typeof pkg.bin === "string")
|
|
240
|
+
return;
|
|
241
|
+
const bins = pkg.bin;
|
|
242
|
+
if (!(app.bins.mcp in bins)) {
|
|
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
|
+
const fix = `Add "${unscoped}": "dist/npx.js" to bin, where src/npx.ts holds only: import "./index.js";`;
|
|
248
|
+
if (new Set(Object.values(bins)).size === 1) {
|
|
249
|
+
add("error", "install", `Every binary runs the same file, so npx -y ${pkg.name} starts whichever one the registry lists first, which may be ${app.bins.cli}. ${fix}`);
|
|
250
|
+
}
|
|
251
|
+
else if (!(unscoped in bins)) {
|
|
252
|
+
add("error", "install", `npx -y ${pkg.name} cannot choose between ${Object.keys(bins).join(", ")}. ${fix}`);
|
|
253
|
+
}
|
|
254
|
+
else if (unscoped === app.bins.cli || unscoped.startsWith(app.bins.cli)) {
|
|
255
|
+
add("error", "install", `npx -y ${pkg.name} starts ${unscoped}, which runs as the CLI. Name the package so it does not start with ${app.bins.cli}.`);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
218
258
|
/** Every command and flag a README or SKILL.md tells someone to type must exist. */
|
|
219
259
|
function checkDocs(app, file, add) {
|
|
220
260
|
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.3",
|
|
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",
|