pypi substack-opsstdioMITupdated 3mo ago
Standalone Substack CLI + 26-tool MCP server for Cursor MCP, Claude MCP, OpenCode MCP, and any stdio MCP host. Your IDE drafts the replies. Zero AI API keys.
substack ops 能做什么?
substack-ops
Standalone Substack CLI + 26-tool MCP server for Cursor MCP, Claude MCP, OpenCode MCP, and any stdio MCP host. Your IDE drafts the replies. Zero AI API keys.
Site → substack-ops.chavan.in · Source → 06ketan/substack-ops · Glama → mcp/servers/06ketan/substack-ops
Posts, notes, comments, replies, reactions, restacks, recommendations, search, profiles, feeds, automations, MCP server, Textual TUI. One Python install, one binary, MIT licensed.
TL;DR — MCP-native (no API key, one command)
uvx substack-ops mcp install cursor # or claude-desktop, claude-code, opencode, print
# Restart your host. Then in chat:
# "list unanswered comments on post 193866852"
# "draft a warm reply to comment 12345"
# "post that draft"
Your host's LLM (Cursor's, Claude's) does the drafting via the
propose_reply / confirm_reply tools. No ANTHROPIC_API_KEY /
OPENAI_API_KEY needed.
Wrong install?
This project is substack-ops on PyPI — install with uv / uvx, not unrelated npx packages that appear when searching “Substack MCP”. Canonical listing: Glama — 06ketan/substack-ops.
Works with (MCP)
These rows help discovery (search keywords); confirm each host’s current MCP docs before upgrading.
Open source–oriented hosts
| Host | Documentation | Typical wire-up |
|---|---|---|
| OpenCode | OpenCode MCP servers | uvx substack-ops mcp install opencode |
| Continue | Continue | uvx substack-ops mcp install print — paste the snippet into Continue’s MCP settings |
| Zed | Zed | Configure stdio MCP per Zed’s docs |
| Cline | Cline | MCP setup per extension / marketplace docs |
| Goose | Goose | MCP extensions per Goose docs |
Large commercial stacks
| Host | Documentation | Typical wire-up |
|---|---|---|
| Cursor | Cursor MCP | uvx substack-ops mcp install cursor |
| Claude (Desktop / Code) | Claude Desktop, Claude Code | mcp install claude-desktop / claude-code |
| GitHub Copilot | Copilot | MCP in VS Code / Copilot where supported — use print + host docs |
| ChatGPT | OpenAI | Developer / connector flows — often REST (Slideshot API) for tools without MCP |
| Google Gemini | Gemini | Gemini CLI / IDE features per Google docs — stdio where supported |
OpenCode (copy-paste)
Auto-install:
uvx substack-ops mcp install opencode
Manual (~/.config/opencode/opencode.json):
{
"mcp": {
"substack-ops": {
"type": "local",
"command": ["uvx", "substack-ops", "mcp", "serve"],
"enabled": true
}
}
}
Optional version pin: use ["uvx", "substack-ops==0.3.5", "mcp", "serve"] (replace with current PyPI release).
Setup (dev / from source)
git clone https://github.com/06ketan/substack-ops && cd substack-ops
uv sync
uv sync --extra mcp # mcp SDK for the MCP server (recommended)
uv sync --extra tui # textual for the TUI
uv sync --extra chrome # pycryptodome + keyring for Chrome cookie auto-grab
Auth defaults to ~/.cursor/mcp.json's mcpServers.substack-api.env. Override
with env or .env. Or use one of the auth flows in auth login / auth setup.
uv run substack-ops auth verify
uv run substack-ops quickstart # 20-step tour
Command surface
Grouped by intent. Every write defaults to --dry-run; flip with
--no-dry-run (and --yes-i-mean-it for the irreversible ones). All writes
land in .cache/audit.jsonl and are dedup-checked against .cache/actions.db.
Auth (4)
| Command | What it does |
|---|---|
auth verify |
Confirm the cookie works; print authed user/pub. |
auth test |
Same as verify, exit non-zero on failure (CI-friendly). |
auth login --browser chrome|brave |
Auto-grab cookie from local Chromium browser via macOS Keychain. |
auth login --email me@x.com |
Email magic-link → paste-the-link interactive flow. |
auth setup |
Interactive paste of connect.sid cookie. |
Read — Posts (8)
| Command | What it does |
|---|---|
posts list [--pub] [--limit] [--sort new|top] |
List posts from a publication (yours by default). |
posts show <id|slug> [--pub] |
Post metadata (title, dates, reactions, comment count). |
posts get --slug <slug> [--pub] |
Same as show but slug-only. |
posts content <id> [--md] [--pub] |
HTML body (auth-aware for paywalled). --md converts to Markdown. |
posts stats <id> |
Engagement counts — reactions, comments. |
posts search <query> [--pub] [--limit] |
Substack-side full-text search. |
posts paywalled <id> [--pub] |
Boolean: is this post paywalled? |
posts react <id> [--off] [--pub] |
Add (or remove with --off) a reaction. Defaults to ❤. |
posts restack <id> [--off] |
Restack a post (Substack does not support unrestack). |
Read — Notes (5)
| Command | What it does |
|---|---|
notes list [--limit] |
Your published Notes. |
notes show <id> |
One note + its reply tree. |
notes publish <body> [--no-dry-run] |
Publish a top-level Note. |
notes react <id> [--off] |
React on any Note. |
notes restack <id> [--off] |
Restack a Note. |
Read + Write — Comments (5)
| Command | What it does |
|---|---|
comments tree <post_id> [--pub] |
Full nested comment tree as table. |
comments export <post_id> --out file.json [--pub] |
Same tree as JSON. |
comments add <post_id> <body> [--pub] [--no-dry-run] |
New top-level comment. |
comments react <id> --kind post|note [--off] |
React on a comment. |
comments delete <id> --kind post|note [--no-dry-run] |
Destructive — your own comments only. |
Reply engine (6)
| Command | What it does |
|---|---|
reply template <post_id> --template thanks |
Rule-based replies (no LLM). |
reply review <post_id> |
LLM drafts each, you [a]ccept / [e]dit / [s]kip / [q]uit. |
reply bulk <post_id> --out drafts.json |
Draft every comment to a file. Edit, set action: "approved". |
reply note-bulk <note_id> --out drafts.json |
Same for replies under a Note. |
reply bulk-send drafts.json [--no-dry-run] |
Posts only approved rows. Dedup-checked. |
reply auto <post_id> --no-dry-run --yes-i-mean-it |
Draft + post immediately. 30s rate limit. |
Read — Discovery (8)
| Command | What it does |
|---|---|
feed list --tab for-you|subscribed|category-{slug} |
Reader feed (the Substack app feed). |
profile me / profile get <handle> |
Profile. |
users get <handle> / users subscriptions <handle> |
Public user info + their subs. |
podcasts list [--pub] |
Audio posts. |
recommendations list [--pub] |
Pub's recommended publications. |
authors list [--pub] |
Pub's contributor list. |
categories list / categories get --name <X> |
Substack's category taxonomy. |
Automations (3)
| Command | What it does |
|---|---|
auto presets |
List built-in YAML rules. |
auto run <name> |
One-shot run a preset. |
auto daemon <name> --interval 60 |
Loop forever; logs to audit. |
Operations + safety (3)
| Command | What it does |
|---|---|
audit search [--kind] [--target] [--status] [--since 7d] |
Query the JSONL audit log. |
audit dedup-status |
Counts in the dedup SQLite DB. |
quickstart |
20-step interactive tour. |
MCP server (3)
| Command | What it does |
|---|---|
mcp install <cursor|claude-desktop|claude-code|opencode|print> [--dry-run] |
Auto-merge config into your host. |
mcp serve |
stdio MCP server (26 tools). |
mcp list-tools |
Print the tool registry. |
Other (1)
| Command | What it does |
|---|---|
tui |
Textual TUI — 6 tabs (Notes, Posts, Comments, Feed, Auto, Profile). |
Multi-publication
Every read command accepts --pub <subdomain|domain>. Defaults to your own
publication.
substack-ops posts list --pub stratechery --limit 5
substack-ops posts search "ai" --pub stratechery
substack-ops recommendations list --pub stratechery
Reply modes
| Mode | What it does | Safety |
|---|---|---|
template |
YAML keyword/regex rules under src/substack_ops/templates/*.yaml |
dry-run default |
review |
LLM drafts each reply, you [a]ccept / [e]dit / [s]kip / [q]uit |
dry-run default + manual gate per comment |
bulk |
LLM drafts every comment to drafts.json. Edit file, set action: "approved" |
offline review, dedup-checked on send |
bulk-send |
Posts only items with action: "approved" |
dry-run default; dedup DB prevents the M2 31-dup-replies regression |
auto |
LLM drafts and posts immediately | requires --no-dry-run --yes-i-mean-it, 30s rate limit |
After every live note-reply the engine re-fetches the new comment and asserts
ancestor_path is non-empty. If empty, the audit row's result_status is
flipped to "orphaned" (the M2 bug where parent_comment_id was silently
dropped — now caught).
Automations
Built-in presets (auto presets):
- like-back — when someone reacts to your note, react to their latest note.
- auto-reply — same trigger, but post a templated thank-you.
- auto-restack — when a watchlist handle posts a new note, restack it.
- follow-back — when someone follows you, follow them back.
Custom YAML rules under ~/.config/substack-ops/auto/*.yaml. Loop with
auto daemon <name> --interval 60.
MCP server
substack-ops mcp install opencode # auto-add to ~/.config/opencode/opencode.json
substack-ops mcp install cursor # auto-add to ~/.cursor/mcp.json
substack-ops mcp install claude-desktop # auto-add to claude_desktop_config.json
substack-ops mcp install claude-code # uses `claude mcp add` under the hood
substack-ops mcp install print # print the snippet only
substack-ops mcp install cursor --dry-run # preview without writing
substack-ops mcp serve # stdio server
substack-ops mcp list-tools # 26 tools
Manual config snippet (if you prefer):
{
"mcpServers": {
"substack-ops": {
"command": "substack-ops",
"args": ["mcp", "serve"]
}
}
}
If the mcp SDK is not installed, the server falls back to a minimal
stdin/stdout JSON-line dispatcher that's still useful for scripting:
echo '{"tool":"list_posts","args":{"limit":3}}' | substack-ops mcp serve
MCP-native draft loop (no API key)
3 tools designed to let your host LLM draft for you:
| Tool | What it does |
|---|---|
get_unanswered_comments |
Returns the worklist: comments where you have not yet replied (any depth). |
propose_reply |
Dry-run only. Returns a token + payload preview. No write. |
confirm_reply |
Posts a previously-proposed reply by token. Idempotent via dedup DB. Token TTL 5 min. |
Differentiator tools (the safety + drafting stack that makes the unattended
mode safe): bulk_draft_replies, send_approved_drafts, audit_search,
dedup_status, get_unanswered_comments, propose_reply, confirm_reply.
LLM strategy
Two layers, both free:
- MCP-native (default). Host LLM drafts via
propose_reply/confirm_reply. No env vars, no API key. Use this for interactive replies. - Subprocess CLI (daemon path). For
reply auto/auto daemonwhen no human is in the loop. Auto-detectsclaude(Claude Code),cursor-agent, orcodexon PATH. Override withSUBSTACK_OPS_LLM_CMD.
There is no paid-API-key path. If you want one, vendor the old _anthropic /
_openai methods from substack-ops v0.2.0 yourself.
Textual TUI
substack-ops tui
6 tabs: Notes / Posts / Comments / Feed / Auto / Profile. Sub-tabs: 1=mine, 2=following, 3=general. Keys: tab, 1-3, ↑/↓, enter, r, l, s, o, q/esc.
Auth methods
substack-ops auth verify # uses mcp.json or env
substack-ops auth login # auto-grab cookies from Chrome (macOS Keychain)
substack-ops auth login --browser brave
substack-ops auth login --email me@x.com # email magic-link, paste-the-link mode
substack-ops auth setup # interactive paste cookies
Architecture
mcp.json | env | Chrome | OTP → auth.py / auth_chrome.py / auth_otp.py
│
.cache/cookies.json
│
SubstackClient (httpx)
│
┌──────┬──────┬───────┬───────┬───────┬──────┬──────┬─────┬──────┐
▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼
posts notes comments feed profile users recs cats ... reply_engine
│
┌───────────────┼────────────┐
▼ ▼ ▼
template ai_review ai_bulk + ai_auto
└───────────────┬────────────┘
▼
base.post_reply / post_note_reply
│
┌────────┼────────┐
▼ ▼ ▼
dedup audit ancestor_path
(SQLite) (jsonl) guardrail
auto/engine.py ────────────────┐
mcp/server.py ──── 23 tools ──┼─── all share SubstackClient
tui/app.py ──── 6 tabs ──┘
Endpoints used
| Action | Method + URL |
|---|---|
| Auth check | GET https://substack.com/api/v1/subscriptions |
| List posts | GET {pub}/api/v1/archive |
| Post by id | GET {pub}/api/v1/posts/by-id/{id} |
| Post by slug | GET {pub}/api/v1/posts/{slug} |
| Post content | same as above; body_html field |
| Post search | GET {pub}/api/v1/archive?search= |
| Comments | GET {pub}/api/v1/post/{id}/comments?all_comments=true |
| Reply to comment | POST {pub}/api/v1/post/{id}/comment body {body, parent_id} |
| Add top-level comment | same with parent_id: null |
| React to post | POST {pub}/api/v1/post/{id}/reaction body {reaction} |
| Restack post | POST https://substack.com/api/v1/restack body {post_id} |
| Restack note | POST https://substack.com/api/v1/restack body {comment_id} |
| Delete post-comment | DELETE {pub}/api/v1/comment/{id} (PUB host) |
| Delete note | DELETE https://substack.com/api/v1/comment/{id} (BARE host) |
| My notes | GET https://substack.com/api/v1/reader/feed/profile/{user_id} |
| Note thread | GET https://substack.com/api/v1/reader/comment/{note_id} |
| Note replies | GET https://substack.com/api/v1/reader/comment/{note_id}/replies |
| Publish note | POST https://substack.com/api/v1/comment/feed body {bodyJson} |
| Reply to note | same with {bodyJson, parent_id} (NOT parent_comment_id — known M2 bug) |
| React to comment | POST {host}/api/v1/comment/{id}/reaction (host = pub for post-comments, substack.com for notes) |
| Recommendations | GET {pub}/api/v1/recommendations/from/{publication_id} |
| Authors | GET {pub}/api/v1/publication/users/ranked?public=true |
| Categories | GET https://substack.com/api/v1/categories |
| User profile | GET https://substack.com/api/v1/user/{handle}/public_profile (auto-redirects on 404) |
| Reader feed | GET https://substack.com/api/v1/reader/feed/{recommended|subscribed|category/{slug}} |
Related MCPs
- slideshot — HTML → slides (PNG / PDF / PPTX); npm
slideshot-mcp. - medium-ops — Medium stories + responses + MCP (PyPI
medium-ops).
Tests
uv run pytest -q # 43 tests, ~0.6s, no live network
Coverage today: auth, client (read+write+engagement+delete), reply engine,
dedup DB, audit log search, MCP tool registry & dispatcher, automation engine
preset loader, the M2 parent_id regression test, the M2 host-mismatch
regression test.
GSD workflow
.planning/ scaffold for Get Shit Done
under ~/.claude/skills/gsd-*. Roadmap at .planning/ROADMAP.md,
per-phase plans at .planning/phases/M*/PHASE.md.
Known gaps
- Full email stats (opens/clicks/views) — needs dashboard CSRF flow. Fallback: Playwright MCP scrape.
- Reactions endpoint shape on POST/DELETE not yet probed live; current shape is a best-guess from upstream tool catalogs.
- Auto-engine
new_follower/new_note_fromtriggers are stubbed (returnnote: "trigger not yet implemented"). - TUI sub-tabs (1/2/3) and reply/like/restack key bindings are scaffolded but not wired to the client yet.
- Chrome cookie auto-grab tested only for macOS Chrome; Brave path included; Linux/Windows not supported.
License
MIT. See LICENSE.
The vendored httpx-port helpers under src/substack_ops/_substack/ are derived
from the MIT-licensed NHagar/substack_api package — kept here so this repo
ships zero runtime dependencies on third-party Substack libraries. Attribution
preserved in each file's module docstring.
安装
把 substack ops 添加到你的客户端。选择你正在使用的那个。
claude mcp add substack-ops -- uvx substack-opscodex mcp add substack-ops -- uvx substack-opsamp mcp add substack-ops -- uvx substack-ops{
"mcpServers": {
"substack-ops": {
"command": "uvx",
"args": [
"substack-ops"
]
}
}
}Add to `claude_desktop_config.json`, then restart Claude Desktop.
{
"mcpServers": {
"substack-ops": {
"command": "uvx",
"args": [
"substack-ops"
]
}
}
}Add to `~/.cursor/mcp.json`, or `.cursor/mcp.json` for a single project.
code --add-mcp '{"name":"substack-ops","command":"uvx","args":["substack-ops"]}'Or add the block manually to `.vscode/mcp.json` under `servers`.
{
"mcpServers": {
"substack-ops": {
"command": "uvx",
"args": [
"substack-ops"
]
}
}
}Add to `~/.codeium/windsurf/mcp_config.json`.
{
"mcpServers": {
"substack-ops": {
"command": "uvx",
"args": [
"substack-ops"
]
}
}
}Add to `cline_mcp_settings.json` via the MCP Servers panel.
{
"mcpServers": {
"substack-ops": {
"command": "uvx",
"args": [
"substack-ops"
]
}
}
}Add to `~/.gemini/settings.json`.
{
"mcpServers": {
"substack-ops": {
"type": "local",
"command": "uvx",
"args": [
"substack-ops"
],
"tools": [
"*"
]
}
}
}Add to `~/.copilot/mcp-config.json`, or run `/mcp add` inside the CLI.
{
"context_servers": {
"substack-ops": {
"command": {
"path": "uvx",
"args": [
"substack-ops"
]
}
}
}
}Add to your Zed `settings.json`.
uvx substack-opsRun `goose configure`, choose **Add Extension → Command-line Extension**, and paste this command.
评分
39 / 100
不完整
- 文档25/25
- 维护19/25
- 可信度13/20
- 能力0/15
- 安装体验12/15
- Documents what it does and how to connect
- Has a resolvable package or endpoint
- Exposes at least one tool, prompt or resource
- README has substantive content
- Includes a code example
- Documents its configuration
- Mentions credentials or security posture
- Last commit 99 days ago
- Has a release history
- Repository is not archived
- Licensed MIT
- Namespace verified in the official MCP registry
- Claimed by its owner
- Published under an organisation
- 0 tool(s) documented
- Provides prompt templates
- Provides resources
- 12 documented install method(s)
- Published to a package registry
- Offers a hosted endpoint — no local install
版本历史
| 版本 | 发布于 |
|---|---|
| 0.3.5最新 | 2026年5月3日 |
| 0.3.4 | 2026年5月2日 |
| 0.3.1 | 2026年4月21日 |