You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 714ffdd
Browse filesBrowse the repository at this point in the historyBrowse files
Copy file name to clipboardExpand all lines: AGENTS.md
+25-8Lines changed: 25 additions & 8 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -31,23 +31,40 @@ and then renders the post with the tags visible as text. There is no error to
31
31
catch. `src/content/` exists entirely because of this, and the round trip is
32
32
tested in `tests/content.test.ts`. Never send a raw string as a body.
33
33
34
-
**Writes are on by default.** Only the irreversible actions require
35
-
`confirm: true`. A server that gates every write teaches the caller to pass the
36
-
flag reflexively, which is worse than no gate. See `src/safety.ts`.
34
+
**Writes are on by default.** Only the public or irreversible actions need
35
+
approval: over MCP a person approves each where the client can ask, and
36
+
`confirm: true` counts only where it cannot. A server that gates every write
37
+
teaches the caller to approve reflexively, which is worse than no gate. Slipway
38
+
runs the guard; `src/tools/kit.ts` decides which tools need it.
37
39
38
-
**Annotations are set on every tool**, because MCP defaults `destructiveHint`
40
+
**Annotations are set on every tool**, by Slipway from each tool's risk, because MCP defaults `destructiveHint`
39
41
and `openWorldHint` to true when omitted, so an unannotated read shows up in a
40
42
client as dangerous.
41
43
42
44
**Note scheduling is local.** Substack schedules posts but not Notes, so the
43
-
queue lives in `src/scheduler.ts` and only fires while the server runs. That
44
-
limit is documented rather than hidden.
45
+
queue lives in `src/scheduler.ts` and only fires while the server runs:
46
+
`onServe` in `src/app.ts` starts it, over stdio or HTTP, and never for a CLI
47
+
command. That limit is documented rather than hidden.
48
+
49
+
## Where things are
50
+
51
+
| Path | What it holds |
52
+
|---|---|
53
+
|`src/app.ts`| The Slipway app: tools, settings, login, doctor, the Note queue. Slipway serves MCP, the CLI and `--http`, and owns the write guard and the audit log |
54
+
|`src/guide.ts`| Server instructions, resources and prompts |
55
+
|`src/doctor.ts`| Session age, publications and a live sign-in per publication |
56
+
|`src/config.ts`| Credentials and the multi-publication model |
57
+
|`src/api/`| The one HTTP client and its typed errors |
58
+
|`src/auth/`|`login` and the encrypted session file |
59
+
|`src/content/`| Markdown and HTML to Substack's ProseMirror body, and back |
60
+
|`src/tools/`| One module per group; `kit.ts` adapts them to Slipway |
45
61
46
62
## Adding a tool
47
63
48
64
One `defineTool` call in the right module under `src/tools/`. Guarding,
49
-
annotations, error handling and publication selection are applied centrally by
50
-
`src/tools/kit.ts`, so a tool describes only what it does.
65
+
annotations and error handling come from Slipway, and `src/tools/kit.ts` adds
66
+
publication selection and turns Substack's errors into exit codes, so a tool
67
+
describes only what it does.
51
68
52
69
Group by what the tool reaches, not by endpoint. Put platform constraints in the
53
70
tool description, where they stay in context, not only in the README.
Copy file name to clipboardExpand all lines: CHANGELOG.md
+19-1Lines changed: 19 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -2,10 +2,28 @@
2
2
3
3
| Component | Version | Last Updated |
4
4
|-----------|---------|--------------|
5
-
| substack-mcp-cli |2.2.3| 2026-10-04|
5
+
| substack-mcp-cli |3.0.0| 2026-10-05|
6
6
7
7
---
8
8
9
+
## 3.0.0, 2026-10-05
10
+
11
+
Built on [Slipway](https://github.com/thenavidm/slipway) 0.1.9. The 65 tools keep their names and arguments, and every difference below was measured against 2.2.3 before release.
12
+
13
+
-**A person approves each publish, delete, Note and comment over MCP.** Claude Code (2.1.246 and later) shows its own prompt for each of the ten tools that are public or cannot be undone, and a client that can show forms asks with an approval form whose one box starts unticked. Approvals are signed, bound to the exact call and work once. Where a client can do neither, the model's `confirm: true` still counts, and `SUBSTACK_CONFIRM=model` makes it enough everywhere, for an agent with no person to ask. The audit log records who approved each write.
14
+
-**A smaller tool list.** 22,833 tokens in Claude Code with every tool loaded, down from 25,215: the per-tool `$schema` line, an `execution` field and `additionalProperties: false` are gone. The last one advertised strict input while unknown keys were dropped anyway; the schema now says what happens.
15
+
-**`idempotentHint` is true only where a repeat changes nothing more.** 2.2.3 set it on every write that was not destructive, so a client could take a retry of `create_draft` as harmless and make a second draft. The seven tools that create something, `create_draft`, `create_draft_from_template`, `create_tag`, `create_template`, `schedule_note`, `upload_image` and `add_subscriber`, now say so; updating, scheduling a draft, tagging and cancelling keep the hint.
16
+
-**Exit codes follow the house contract everywhere.** A refused publish exits 2 instead of 5, an unknown command and a write in read-only mode 2 instead of 1, and `doctor` with nothing configured 10 instead of 1. 1 now means an unexpected error. Errors keep what Substack said in `details`, and a rate limit says how long to wait.
17
+
-**`which <words>` finds a command without the full list**, and `agent-context` describes every command, flag and setting as JSON. In Codex, finding the command that schedules a Note and the flags it requires took 83,422 input tokens instead of 105,216 (median of five), in three commands every time. 2.2.3's help listed no commands, so the model matched that only when it guessed the name; otherwise it read the full list or ran a command that does not exist. Over MCP the same task read 24 fewer out of about 48,500.
18
+
-**`install <client>`** adds the server to Claude Code, Codex, Claude Desktop, Cursor, VS Code or Gemini CLI in each one's own format, naming only the settings that connect a publication.
19
+
-**Scheduled Notes still go out from a running server**, over stdio or HTTP, and never from a terminal command, now through Slipway's `onServe`.
20
+
-**Less work to start.** The entry turns on Node's compile cache, and the server spends 173 ms of CPU before its first answer where 2.2.3 spent 202 (median of 21 runs, taking turns on one busy Mac). npx installs 4 dependencies instead of 94.
21
+
-**Docs fixes.** INSTALL.md installed `@thenavidm/substack-mcp`, the package this one replaced, and SECURITY.md and INSTALL.md linked to a repository that does not exist. The README pointed Docker at an image that was never published, carried two stale copies of the variable table inside Troubleshooting and left `get_growth_sources` out of the tools; all fixed, with a Features table. The release workflow attaches the desktop extension, the icon and terminal recording load from cdn.navid.me, and THIRD_PARTY_NOTICES.md lists the production dependencies' licenses.
22
+
23
+
### Upgrading
24
+
25
+
Node 22 or newer; 2.2.3 ran on 20. Scripts keep working for success, usage errors and missing setup; one that treated exit 5 as a refused publish, or 1 as an unknown command or read-only mode, should read 2. Over MCP, expect an approval prompt or form for each publish, delete, Note and comment; a headless agent that should act with `confirm: true` alone needs `SUBSTACK_CONFIRM=model`. A script that pipes JSON-RPC into the server must keep stdin open until it reads the answer: the server now stops when its input ends, as the MCP stdio binding asks. The HTTP settings are `SUBSTACK_HTTP_PORT`, `_HOST`, `_TOKEN` and `_ALLOWED_ORIGINS`; 2.2's `SUBSTACK_MCP_` names still work, and the new ones win when both are set. `--http` will not start on an address other than localhost without a token, where 2.2.3 only warned. Some terminal screens grew: the general help by 218 tokens, for `which`, `install`, the flags, the exit codes and the safety settings it now lists; the command list by 24, for the lines that point to `which` and `--help`; `create-draft --help` by 16, for the defaults it now shows and `--dry-run`, which is new; and a missing argument's error by 15, for its code and the help to read.
26
+
9
27
## 2.2.3, 2026-10-04
10
28
11
29
-**`npx -y @thenavidm/substack-mcp-cli` starts the MCP server whatever order npm keeps.** npx starts whichever binary the npm registry lists first when they share one file, and the registry does not keep the published order. For this package that happened to be the server; for 23 others it was the CLI. A third binary named after the package, on its own file, now always starts the server, and npx picks it by name.
@@ -38,15 +38,15 @@ Your `connect.sid` cookie is full access to your Substack account. Treat it like
38
38
### Fastest
39
39
40
40
```bash
41
-
npx @thenavidm/substack-mcp login
41
+
npx @thenavidm/substack-mcp-cli login
42
42
```
43
43
44
44
Prompts for the publication URL and the cookie, resolves your user id, and stores it encrypted in `~/.substack-mcp/session.json`. After that you can leave the `env` block out of your client config entirely.
Slowest by a distance. Use it on a machine with no Chrome, or in CI.
@@ -73,7 +73,7 @@ Disable ad blockers first. Some strip the cookie from that panel.
73
73
## Verify
74
74
75
75
```bash
76
-
npx @thenavidm/substack-mcp doctor
76
+
npx @thenavidm/substack-mcp-cli doctor
77
77
```
78
78
79
79
Checks credentials, configuration, connectivity and byline resolution, and names whatever is wrong.
@@ -82,11 +82,12 @@ Checks credentials, configuration, connectivity and byline resolution, and names
82
82
83
83
| Variable | Effect |
84
84
|---|---|
85
-
|`SUBSTACK_READ_ONLY=1`| Only the 38 read tools are exposed |
85
+
|`SUBSTACK_READ_ONLY=1`| Only the 41 read tools are exposed |
86
86
|`SUBSTACK_ALLOW_DESTRUCTIVE=0`| Drafting works, publishing and deleting do not |
87
-
|`SUBSTACK_AUDIT_LOG=/path/to/log`| Append-only record of every attempted write |
87
+
|`SUBSTACK_AUDIT_LOG=/path/to/log`| Append-only record of every attempted write, and who approved it |
88
+
|`SUBSTACK_CONFIRM=model`| Lets `confirm: true` alone approve over MCP, for an agent with no person to ask |
88
89
89
-
Irreversible actions require `confirm: true` regardless of these settings.
90
+
Publishing, deleting, Notes and comments wait for your approval whatever these settings say: Claude Code shows its own prompt for each, a client that can show forms asks with one, and elsewhere the model must pass `confirm: true`.
90
91
91
92
## When it stops working
92
93
@@ -97,4 +98,4 @@ If you are on a custom domain and get a 403 mentioning `error code: 1010`, that
0 commit comments