@thenavidm/slipway 0.1.2 → 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 CHANGED
@@ -2,6 +2,10 @@
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
+
5
9
  ## 0.1.2, 2026-10-04: clients always start the server
6
10
 
7
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.
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.
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 both binaries on the same file: `"<name>-mcp"` and `"<name>-cli"`, both `dist/index.js`.
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/check.js CHANGED
@@ -219,9 +219,10 @@ async function checkParityIn(era, app, env, tools, add) {
219
219
  }
220
220
  /**
221
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.
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.
225
226
  */
226
227
  function checkBins(app, file, add) {
227
228
  let pkg;
@@ -238,19 +239,20 @@ function checkBins(app, file, add) {
238
239
  if (!pkg.bin || typeof pkg.bin === "string")
239
240
  return;
240
241
  const bins = pkg.bin;
241
- const keys = Object.keys(bins);
242
- if (!keys.includes(app.bins.mcp)) {
242
+ if (!(app.bins.mcp in bins)) {
243
243
  add("error", "install", `package.json has no ${app.bins.mcp} binary, so clients cannot start the server by name.`);
244
244
  return;
245
245
  }
246
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.`);
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}`);
251
250
  }
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.`);
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}.`);
254
256
  }
255
257
  }
256
258
  /** Every command and flag a README or SKILL.md tells someone to type must exist. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thenavidm/slipway",
3
- "version": "0.1.2",
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",