Skip to content

Commit 714ffdd

Browse files
committed
Release 3.0.0: built on Slipway, measured against 2.2.3
1 parent a35da4b commit 714ffdd

29 files changed

Lines changed: 832 additions & 2936 deletions

‎.github/workflows/ci.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ jobs:
1414
strategy:
1515
matrix:
1616
os: [ubuntu-latest, windows-latest]
17-
node: [20, 22]
17+
node: [22, 24]
1818
runs-on: ${{ matrix.os }}
1919
timeout-minutes: 15
2020
steps:

‎.github/workflows/publish.yml‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,15 @@ jobs:
6666
runs-on: ubuntu-latest
6767
steps:
6868
- uses: actions/checkout@v4
69+
- uses: actions/setup-node@v4
70+
with:
71+
node-version: 22
72+
cache: npm
73+
- run: npm ci
74+
# The README sends Claude Desktop users to the latest release for the .mcpb,
75+
# so the release carries it.
76+
- run: npm run build:mcpb
6977
- name: Create the GitHub release
70-
run: gh release create "$GITHUB_REF_NAME" --title "$GITHUB_REF_NAME" --notes-file CHANGELOG.md
78+
run: gh release create "$GITHUB_REF_NAME" "desktop-extension/substack-${GITHUB_REF_NAME#v}.mcpb" --title "$GITHUB_REF_NAME" --notes-file CHANGELOG.md
7179
env:
7280
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

‎AGENTS.md‎

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,23 +31,40 @@ and then renders the post with the tags visible as text. There is no error to
3131
catch. `src/content/` exists entirely because of this, and the round trip is
3232
tested in `tests/content.test.ts`. Never send a raw string as a body.
3333

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.
3739

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`
3941
and `openWorldHint` to true when omitted, so an unannotated read shows up in a
4042
client as dangerous.
4143

4244
**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 |
4561

4662
## Adding a tool
4763

4864
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.
5168

5269
Group by what the tool reaches, not by endpoint. Put platform constraints in the
5370
tool description, where they stay in context, not only in the README.

‎CHANGELOG.md‎

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,28 @@
22

33
| Component | Version | Last Updated |
44
|-----------|---------|--------------|
5-
| substack-mcp-cli | 2.2.3 | 2026-10-04 |
5+
| substack-mcp-cli | 3.0.0 | 2026-10-05 |
66

77
---
88

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+
927
## 2.2.3, 2026-10-04
1028

1129
- **`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.

‎INSTALL.md‎

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@ Four discovery tools work with no login at all: `search_publications`, `get_publ
66

77
## Prerequisites
88

9-
Node 20 or newer. Nothing else.
9+
Node 22 or newer. Nothing else.
1010

1111
## Install
1212

1313
```bash
14-
claude mcp add substack -- npx -y @thenavidm/substack-mcp
14+
claude mcp add substack -- npx -y @thenavidm/substack-mcp-cli
1515
```
1616

1717
Or in any client's MCP config:
@@ -21,7 +21,7 @@ Or in any client's MCP config:
2121
"mcpServers": {
2222
"substack": {
2323
"command": "npx",
24-
"args": ["-y", "@thenavidm/substack-mcp"],
24+
"args": ["-y", "@thenavidm/substack-mcp-cli"],
2525
"env": {
2626
"SUBSTACK_PUBLICATION_URL": "example.substack.com",
2727
"SUBSTACK_SESSION_TOKEN": "your-connect-sid-value"
@@ -38,15 +38,15 @@ Your `connect.sid` cookie is full access to your Substack account. Treat it like
3838
### Fastest
3939

4040
```bash
41-
npx @thenavidm/substack-mcp login
41+
npx @thenavidm/substack-mcp-cli login
4242
```
4343

4444
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.
4545

4646
### From the browser you already have open
4747

4848
```bash
49-
npx @thenavidm/substack-mcp login --playwriter
49+
npx @thenavidm/substack-mcp-cli login --playwriter
5050
```
5151

5252
Reads the cookie out of your running Chrome. Needs [Playwriter](https://playwriter.dev) and its extension.
@@ -55,7 +55,7 @@ Reads the cookie out of your running Chrome. Needs [Playwriter](https://playwrit
5555

5656
```bash
5757
npm i -g playwright && npx playwright install chromium
58-
npx @thenavidm/substack-mcp login --playwright
58+
npx @thenavidm/substack-mcp-cli login --playwright
5959
```
6060

6161
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.
7373
## Verify
7474

7575
```bash
76-
npx @thenavidm/substack-mcp doctor
76+
npx @thenavidm/substack-mcp-cli doctor
7777
```
7878

7979
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
8282

8383
| Variable | Effect |
8484
|---|---|
85-
| `SUBSTACK_READ_ONLY=1` | Only the 38 read tools are exposed |
85+
| `SUBSTACK_READ_ONLY=1` | Only the 41 read tools are exposed |
8686
| `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 |
8889

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`.
9091

9192
## When it stops working
9293

@@ -97,4 +98,4 @@ If you are on a custom domain and get a 403 mentioning `error code: 1010`, that
9798
## Links
9899

99100
- [Substack](https://substack.com)
100-
- [Repository](https://github.com/thenavidm/substack-mcp)
101+
- [Repository](https://github.com/thenavidm/substack-mcp-cli)

0 commit comments

Comments
 (0)