@thenavidm/slipway 0.1.11 → 0.1.12
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 +4 -0
- package/README.md +3 -1
- package/dist/cli/context.js +10 -5
- package/dist/cli/help.js +13 -8
- package/dist/docs.js +10 -5
- package/dist/policy.d.ts +13 -0
- package/dist/policy.js +16 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
What changed in Slipway, newest first.
|
|
4
4
|
|
|
5
|
+
## 0.1.12, 2026-10-05: what the Meta Ad Library needed
|
|
6
|
+
|
|
7
|
+
- **The write switches appear only where they act.** `<PREFIX>_READ_ONLY` and `<PREFIX>_AUDIT_LOG` are listed in the general help, `agent-context` and the generated settings table only when a tool writes, and `<PREFIX>_ALLOW_DESTRUCTIVE` and `<PREFIX>_CONFIRM` only when a tool can be irreversible, spends money or needs confirming. The Meta Ad Library server only reads, and its help offered to "refuse the irreversible writes"; it is now 40 tokens shorter, 399 against 439. The switches still work if set.
|
|
8
|
+
|
|
5
9
|
## 0.1.11, 2026-10-05: what WordPress needed
|
|
6
10
|
|
|
7
11
|
- **Records no longer advertise `propertyNames`.** Zod 4 gives every `z.record` `propertyNames: { type: "string" }`, which every JSON object key already is, and the Zod 3 converters never wrote it. Slipway leaves it out of what clients receive, as it does the safe-integer bounds, and validation still runs on the schema itself. Each one cost 8 tokens; WordPress has nine. An argument that happens to be named `propertyNames` is a name, so it stays.
|
package/README.md
CHANGED
|
@@ -577,12 +577,14 @@ See [CHANGELOG.md](CHANGELOG.md).
|
|
|
577
577
|
| [Google Photos](https://github.com/thenavidm/google-photos-mcp-cli) | [`@thenavidm/google-photos-mcp-cli`](https://www.npmjs.com/package/@thenavidm/google-photos-mcp-cli) 2.0.0 | The photo picker, uploads, albums and their captions, places and maps, and media this server uploaded, across several Google accounts |
|
|
578
578
|
| [Mastodon](https://github.com/thenavidm/mastodon-mcp-cli) | [`@thenavidm/mastodon-mcp-cli`](https://www.npmjs.com/package/@thenavidm/mastodon-mcp-cli) 2.0.0 | Posting, editing, threads, timelines, search, lists, notifications and following, on any instance |
|
|
579
579
|
| [Midjourney](https://github.com/thenavidm/midjourney-mcp-cli) | [`@thenavidm/midjourney-mcp-cli`](https://www.npmjs.com/package/@thenavidm/midjourney-mcp-cli) 2.0.0 | Generating images and video, following jobs, downloads, moodboards, the account's library and the explore feeds, through a signed-in browser |
|
|
580
|
+
| [Podcast Index](https://github.com/thenavidm/podcastindex-mcp-cli) | [`@thenavidm/podcastindex-mcp-cli`](https://www.npmjs.com/package/@thenavidm/podcastindex-mcp-cli) 2.0.0 | The open podcast directory, with the transcripts and chapters it links to fetched and read, guest histories, feed health and value-for-value |
|
|
580
581
|
| [Substack](https://github.com/thenavidm/substack-mcp-cli) | [`@thenavidm/substack-mcp-cli`](https://www.npmjs.com/package/@thenavidm/substack-mcp-cli) 3.0.0 | Drafts, publishing and scheduling, Notes, subscribers, analytics, tags, comments and researching other publications |
|
|
581
582
|
| [Teachable](https://github.com/thenavidm/teachable-mcp-cli) | [`@thenavidm/teachable-mcp-cli`](https://www.npmjs.com/package/@thenavidm/teachable-mcp-cli) 3.0.0 | Courses, users, enrollments, pricing, coupons and transactions |
|
|
582
583
|
| [Threads](https://github.com/thenavidm/threads-mcp-cli) | [`@thenavidm/threads-mcp-cli`](https://www.npmjs.com/package/@thenavidm/threads-mcp-cli) 2.0.0 | Posting, threads, carousels, replies and reply approvals, insights and keyword search |
|
|
583
584
|
| [TikTok](https://github.com/thenavidm/tiktok-mcp-cli) | [`@thenavidm/tiktok-mcp-cli`](https://www.npmjs.com/package/@thenavidm/tiktok-mcp-cli) 2.0.0 | Your own account's profile and videos, ranked by any metric, posting and drafts, through TikTok's official API |
|
|
584
585
|
| [ThriveCart](https://github.com/thenavidm/thrivecart-mcp-cli) | [`@thenavidm/thrivecart-mcp-cli`](https://www.npmjs.com/package/@thenavidm/thrivecart-mcp-cli) 3.0.0 | Products and pricing, transactions and revenue, customers, subscriptions and affiliates, across several carts |
|
|
585
|
-
| [
|
|
586
|
+
| [WordPress](https://github.com/thenavidm/wordpress-mcp-cli) | [`@thenavidm/wordpress-mcp-cli`](https://www.npmjs.com/package/@thenavidm/wordpress-mcp-cli) 2.0.0 | Posts, pages, custom post types, media, terms, users and comments across several sites, plus Elementor, Rank Math, redirects and bulk edits through a helper plugin |
|
|
587
|
+
| [YouTube](https://github.com/thenavidm/youtube-mcp-cli) | [`@thenavidm/youtube-mcp-cli`](https://www.npmjs.com/package/@thenavidm/youtube-mcp-cli) 3.0.1 | Transcripts of any public video, channel research against each channel's own median, and your own channels' videos, comments and analytics |
|
|
586
588
|
|
|
587
589
|
Each server was measured against its previous release before it moved: startup, what a client receives, CLI exit codes, and tokens in Claude Code and Codex. Its README has the numbers.
|
|
588
590
|
|
package/dist/cli/context.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { createRequire } from "node:module";
|
|
9
9
|
import { EXIT } from "../errors.js";
|
|
10
|
-
import { policyEnvNames } from "../policy.js";
|
|
10
|
+
import { policyEnvNames, switchesThatApply } from "../policy.js";
|
|
11
11
|
import { outputJsonSchema } from "../schema.js";
|
|
12
12
|
import { exampleCommand, GLOBAL_FLAGS } from "./help.js";
|
|
13
13
|
import { flagsFor } from "./flags.js";
|
|
@@ -26,6 +26,7 @@ export const EXIT_MEANINGS = {
|
|
|
26
26
|
export function agentContext(app, env, bin, options = {}) {
|
|
27
27
|
const policy = app.policy(env);
|
|
28
28
|
const names = policyEnvNames(app.envPrefix);
|
|
29
|
+
const applies = switchesThatApply(app.allTools);
|
|
29
30
|
const tools = app.tools(env);
|
|
30
31
|
const hidden = app.allTools.length - tools.length;
|
|
31
32
|
// Terminal commands the app adds beside its tools, such as logout, and a sign-in flow that says what it takes.
|
|
@@ -78,15 +79,19 @@ export function agentContext(app, env, bin, options = {}) {
|
|
|
78
79
|
...(setting.secret ? { secret: true } : {}),
|
|
79
80
|
description: setting.description,
|
|
80
81
|
})),
|
|
81
|
-
{ env: names.readOnly, value: policy.readOnly, description: "hide and refuse every write" },
|
|
82
|
-
|
|
82
|
+
...(applies.readOnly ? [{ env: names.readOnly, value: policy.readOnly, description: "hide and refuse every write" }] : []),
|
|
83
|
+
...(applies.allowDestructive
|
|
84
|
+
? [{ env: names.allowDestructive, value: policy.allowDestructive, description: app.allTools.some((tool) => tool.spends) ? "allow public or irreversible writes and paid calls" : "allow public or irreversible writes" }]
|
|
85
|
+
: []),
|
|
83
86
|
...(app.allTools.some((tool) => tool.tags.length > 0)
|
|
84
87
|
? [{ env: names.toolsets, value: policy.toolsets === "all" ? "all" : [...policy.toolsets], description: "toolsets that are on" }]
|
|
85
88
|
: []),
|
|
86
89
|
{ env: names.surface, value: policy.surface, description: "full tool list, or search for very large catalogs" },
|
|
87
|
-
{ env: names.auditLog, value: policy.auditLog ?? null, description: "file that records every attempted write" },
|
|
90
|
+
...(applies.auditLog ? [{ env: names.auditLog, value: policy.auditLog ?? null, description: "file that records every attempted write" }] : []),
|
|
88
91
|
{ env: names.toolTimeoutMs, value: policy.toolTimeoutMs ?? null, description: "deadline for any tool" },
|
|
89
|
-
|
|
92
|
+
...(applies.confirm
|
|
93
|
+
? [{ env: names.confirm, value: policy.confirm, description: "who confirms a confirmed call over MCP: human asks a person where the client can, model accepts confirm: true" }]
|
|
94
|
+
: []),
|
|
90
95
|
{ env: `${app.envPrefix}_HTTP_PORT`, value: env[`${app.envPrefix}_HTTP_PORT`] ?? null, description: `port for --http, ${app.definition.httpPort ?? 8787} when unset` },
|
|
91
96
|
{ env: `${app.envPrefix}_HTTP_HOST`, value: env[`${app.envPrefix}_HTTP_HOST`] ?? null, description: "address for --http, 127.0.0.1 when unset; any other needs a token" },
|
|
92
97
|
{ env: `${app.envPrefix}_HTTP_TOKEN`, set: Boolean(env[`${app.envPrefix}_HTTP_TOKEN`]), secret: true, description: "bearer token --http requires" },
|
package/dist/cli/help.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* shows what applies and points to the rest instead of repeating it.
|
|
6
6
|
*/
|
|
7
7
|
import { EXIT } from "../errors.js";
|
|
8
|
-
import { policyEnvNames, riskMark, visibility } from "../policy.js";
|
|
8
|
+
import { policyEnvNames, riskMark, switchesThatApply, visibility } from "../policy.js";
|
|
9
9
|
import { firstSentence } from "../search.js";
|
|
10
10
|
import { flagsFor } from "./flags.js";
|
|
11
11
|
const COLUMN = 30;
|
|
@@ -205,6 +205,7 @@ function table(rows) {
|
|
|
205
205
|
}
|
|
206
206
|
export function renderGeneralHelp(app, bin) {
|
|
207
207
|
const names = policyEnvNames(app.envPrefix);
|
|
208
|
+
const applies = switchesThatApply(app.allTools);
|
|
208
209
|
const cache = app.allTools.some((tool) => tool.cache);
|
|
209
210
|
const sync = app.allTools.some((tool) => tool.sync);
|
|
210
211
|
const jobs = app.allTools.some((tool) => tool.job);
|
|
@@ -236,20 +237,24 @@ export function renderGeneralHelp(app, bin) {
|
|
|
236
237
|
const tuning = [
|
|
237
238
|
...(app.definition.settings ?? []).filter((setting) => setting.tuning).map((setting) => setting.env),
|
|
238
239
|
names.surface,
|
|
239
|
-
names.auditLog,
|
|
240
|
+
...(applies.auditLog ? [names.auditLog] : []),
|
|
240
241
|
names.toolTimeoutMs,
|
|
241
|
-
names.confirm,
|
|
242
|
+
...(applies.confirm ? [names.confirm] : []),
|
|
242
243
|
...(cache ? [names.cache] : []),
|
|
243
244
|
...(cache || sync ? [names.dataDir] : []),
|
|
244
245
|
`${app.envPrefix}_DEBUG`,
|
|
245
246
|
];
|
|
246
247
|
const settings = [
|
|
247
248
|
...(app.definition.settings ?? []).filter((setting) => !setting.tuning).map((setting) => [setting.env, setting.description]),
|
|
248
|
-
[`${names.readOnly}=1`, "hide and refuse every write"],
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
249
|
+
...(applies.readOnly ? [[`${names.readOnly}=1`, "hide and refuse every write"]] : []),
|
|
250
|
+
...(applies.allowDestructive
|
|
251
|
+
? [
|
|
252
|
+
[
|
|
253
|
+
`${names.allowDestructive}=0`,
|
|
254
|
+
`${app.definition.defaults?.destructiveOff === "hide" ? "hide and refuse" : "refuse"} the irreversible writes${app.allTools.some((tool) => tool.spends) ? " and paid calls" : ""}`,
|
|
255
|
+
],
|
|
256
|
+
]
|
|
257
|
+
: []),
|
|
253
258
|
// With no tagged tool every tool is always on, so the switch would do nothing.
|
|
254
259
|
...(app.allTools.some((tool) => tool.tags.length > 0) ? [[`${names.toolsets}=a,b`, "only these toolsets, or all"]] : []),
|
|
255
260
|
[`${app.envPrefix}_HTTP_PORT / _HOST / _TOKEN / _ALLOWED_ORIGINS`, "for --http"],
|
package/dist/docs.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { flagsFor } from "./cli/flags.js";
|
|
9
9
|
import { exampleCommand } from "./cli/help.js";
|
|
10
|
-
import { policyEnvNames } from "./policy.js";
|
|
10
|
+
import { policyEnvNames, switchesThatApply } from "./policy.js";
|
|
11
11
|
function cell(text) {
|
|
12
12
|
return text.replace(/\|/g, "\\|").replace(/\s*\n\s*/g, " ").trim() || "None";
|
|
13
13
|
}
|
|
@@ -47,17 +47,22 @@ export function toolReference(app, tools, heading = "###") {
|
|
|
47
47
|
}
|
|
48
48
|
export function settingsTable(app) {
|
|
49
49
|
const names = policyEnvNames(app.envPrefix);
|
|
50
|
+
const applies = switchesThatApply(app.allTools);
|
|
50
51
|
return [
|
|
51
52
|
"| Variable | What it does |",
|
|
52
53
|
"|---|---|",
|
|
53
54
|
...(app.definition.settings ?? []).map((setting) => `| \`${setting.env}\` | ${cell(setting.description)}${setting.secret ? " Keep it private." : ""} |`),
|
|
54
|
-
`| \`${names.readOnly}=1\` | Hide and refuse every write
|
|
55
|
-
|
|
55
|
+
...(applies.readOnly ? [`| \`${names.readOnly}=1\` | Hide and refuse every write |`] : []),
|
|
56
|
+
...(applies.allowDestructive
|
|
57
|
+
? [`| \`${names.allowDestructive}=0\` | Keep writes, refuse the public or irreversible ones${app.allTools.some((tool) => tool.spends) ? " and paid calls" : ""} |`]
|
|
58
|
+
: []),
|
|
56
59
|
`| \`${names.toolsets}\` | Comma-separated toolsets to turn on, or \`all\` |`,
|
|
57
60
|
`| \`${names.surface}=search\` | List three tools that find, describe and run the rest |`,
|
|
58
|
-
`| \`${names.auditLog}\` | File that records every attempted write
|
|
61
|
+
...(applies.auditLog ? [`| \`${names.auditLog}\` | File that records every attempted write |`] : []),
|
|
59
62
|
`| \`${names.toolTimeoutMs}\` | Give up on any tool after this many milliseconds |`,
|
|
60
|
-
|
|
63
|
+
...(applies.confirm
|
|
64
|
+
? [`| \`${names.confirm}=model\` | Let \`confirm: true\` alone confirm a call, for an agent with no person to ask. The default, \`human\`, asks a person wherever the client can |`]
|
|
65
|
+
: []),
|
|
61
66
|
].join("\n");
|
|
62
67
|
}
|
|
63
68
|
export function renderDocs(app, env = process.env) {
|
package/dist/policy.d.ts
CHANGED
|
@@ -62,6 +62,19 @@ export type PolicyEnv = {
|
|
|
62
62
|
cache: string;
|
|
63
63
|
dataDir: string;
|
|
64
64
|
};
|
|
65
|
+
/**
|
|
66
|
+
* Which of the write switches can change anything for these tools. A server
|
|
67
|
+
* whose tools all read has nothing for read-only mode to hide or the audit log
|
|
68
|
+
* to record, and one with no irreversible, paid or confirmed call has nothing
|
|
69
|
+
* for the destructive and confirm switches to act on, so help, agent-context
|
|
70
|
+
* and the generated docs leave those out. They still work if set.
|
|
71
|
+
*/
|
|
72
|
+
export declare function switchesThatApply(tools: readonly Tool[]): {
|
|
73
|
+
readOnly: boolean;
|
|
74
|
+
allowDestructive: boolean;
|
|
75
|
+
auditLog: boolean;
|
|
76
|
+
confirm: boolean;
|
|
77
|
+
};
|
|
65
78
|
export declare function policyEnvNames(prefix: string): PolicyEnv;
|
|
66
79
|
export declare function readPolicy(env: NodeJS.ProcessEnv, prefix: string, defaults?: PolicyDefaults): Policy;
|
|
67
80
|
export type Visibility = {
|
package/dist/policy.js
CHANGED
|
@@ -6,6 +6,22 @@
|
|
|
6
6
|
* block the irreversible ones, keep a log, load fewer tools. They live under
|
|
7
7
|
* the server's own prefix, so two servers in one client never share a switch.
|
|
8
8
|
*/
|
|
9
|
+
/**
|
|
10
|
+
* Which of the write switches can change anything for these tools. A server
|
|
11
|
+
* whose tools all read has nothing for read-only mode to hide or the audit log
|
|
12
|
+
* to record, and one with no irreversible, paid or confirmed call has nothing
|
|
13
|
+
* for the destructive and confirm switches to act on, so help, agent-context
|
|
14
|
+
* and the generated docs leave those out. They still work if set.
|
|
15
|
+
*/
|
|
16
|
+
export function switchesThatApply(tools) {
|
|
17
|
+
const writes = tools.some((tool) => tool.risk !== "read");
|
|
18
|
+
return {
|
|
19
|
+
readOnly: writes,
|
|
20
|
+
allowDestructive: tools.some((tool) => tool.risk === "destructive" || tool.spends),
|
|
21
|
+
auditLog: writes,
|
|
22
|
+
confirm: tools.some((tool) => tool.requireConfirm),
|
|
23
|
+
};
|
|
24
|
+
}
|
|
9
25
|
export function policyEnvNames(prefix) {
|
|
10
26
|
return {
|
|
11
27
|
readOnly: `${prefix}_READ_ONLY`,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thenavidm/slipway",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.12",
|
|
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",
|