substack-api-mcp 1.0.2__tar.gz → 1.0.4__tar.gz

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.
Files changed (101) hide show
  1. {substack_api_mcp-1.0.2 → substack_api_mcp-1.0.4}/PKG-INFO +44 -32
  2. {substack_api_mcp-1.0.2 → substack_api_mcp-1.0.4}/README.md +43 -31
  3. substack_api_mcp-1.0.4/mcp.json +11 -0
  4. {substack_api_mcp-1.0.2 → substack_api_mcp-1.0.4}/pyproject.toml +2 -1
  5. {substack_api_mcp-1.0.2 → substack_api_mcp-1.0.4}/substack_api_mcp/__init__.py +1 -1
  6. substack_api_mcp-1.0.2/mcp.json +0 -8
  7. {substack_api_mcp-1.0.2 → substack_api_mcp-1.0.4}/.github/workflows/ci.yml +0 -0
  8. {substack_api_mcp-1.0.2 → substack_api_mcp-1.0.4}/.github/workflows/publish.yml +0 -0
  9. {substack_api_mcp-1.0.2 → substack_api_mcp-1.0.4}/.gitignore +0 -0
  10. {substack_api_mcp-1.0.2 → substack_api_mcp-1.0.4}/AGENTS.md +0 -0
  11. {substack_api_mcp-1.0.2 → substack_api_mcp-1.0.4}/CLAUDE.md +0 -0
  12. {substack_api_mcp-1.0.2 → substack_api_mcp-1.0.4}/LICENSE +0 -0
  13. {substack_api_mcp-1.0.2 → substack_api_mcp-1.0.4}/assets/logo-white.svg +0 -0
  14. {substack_api_mcp-1.0.2 → substack_api_mcp-1.0.4}/assets/logo.svg +0 -0
  15. {substack_api_mcp-1.0.2 → substack_api_mcp-1.0.4}/examples/add_subscribers.py +0 -0
  16. {substack_api_mcp-1.0.2 → substack_api_mcp-1.0.4}/examples/daily_stats.py +0 -0
  17. {substack_api_mcp-1.0.2 → substack_api_mcp-1.0.4}/examples/draft_from_markdown.py +0 -0
  18. {substack_api_mcp-1.0.2 → substack_api_mcp-1.0.4}/examples/export_subscribers.py +0 -0
  19. {substack_api_mcp-1.0.2 → substack_api_mcp-1.0.4}/examples/get_post.py +0 -0
  20. {substack_api_mcp-1.0.2 → substack_api_mcp-1.0.4}/examples/list_posts.py +0 -0
  21. {substack_api_mcp-1.0.2 → substack_api_mcp-1.0.4}/examples/list_subscribers.py +0 -0
  22. {substack_api_mcp-1.0.2 → substack_api_mcp-1.0.4}/examples/post_stats.py +0 -0
  23. {substack_api_mcp-1.0.2 → substack_api_mcp-1.0.4}/examples/publish_note.py +0 -0
  24. {substack_api_mcp-1.0.2 → substack_api_mcp-1.0.4}/examples/reply_to_comment.py +0 -0
  25. {substack_api_mcp-1.0.2 → substack_api_mcp-1.0.4}/examples/schedule_post.py +0 -0
  26. {substack_api_mcp-1.0.2 → substack_api_mcp-1.0.4}/examples/traffic_sources.py +0 -0
  27. {substack_api_mcp-1.0.2 → substack_api_mcp-1.0.4}/examples/unanswered_comments.py +0 -0
  28. {substack_api_mcp-1.0.2 → substack_api_mcp-1.0.4}/examples/weekly_report.py +0 -0
  29. {substack_api_mcp-1.0.2 → substack_api_mcp-1.0.4}/glama.json +0 -0
  30. {substack_api_mcp-1.0.2 → substack_api_mcp-1.0.4}/substack_api_mcp/api.py +0 -0
  31. {substack_api_mcp-1.0.2 → substack_api_mcp-1.0.4}/substack_api_mcp/app.py +0 -0
  32. {substack_api_mcp-1.0.2 → substack_api_mcp-1.0.4}/substack_api_mcp/cli.py +0 -0
  33. {substack_api_mcp-1.0.2 → substack_api_mcp-1.0.4}/substack_api_mcp/core/__init__.py +0 -0
  34. {substack_api_mcp-1.0.2 → substack_api_mcp-1.0.4}/substack_api_mcp/core/account_guard.py +0 -0
  35. {substack_api_mcp-1.0.2 → substack_api_mcp-1.0.4}/substack_api_mcp/core/context.py +0 -0
  36. {substack_api_mcp-1.0.2 → substack_api_mcp-1.0.4}/substack_api_mcp/core/errors.py +0 -0
  37. {substack_api_mcp-1.0.2 → substack_api_mcp-1.0.4}/substack_api_mcp/core/helpers.py +0 -0
  38. {substack_api_mcp-1.0.2 → substack_api_mcp-1.0.4}/substack_api_mcp/core/tools.py +0 -0
  39. {substack_api_mcp-1.0.2 → substack_api_mcp-1.0.4}/substack_api_mcp/mcp_context.py +0 -0
  40. {substack_api_mcp-1.0.2 → substack_api_mcp-1.0.4}/substack_api_mcp/mcp_server.py +0 -0
  41. {substack_api_mcp-1.0.2 → substack_api_mcp-1.0.4}/substack_api_mcp/runner.py +0 -0
  42. {substack_api_mcp-1.0.2 → substack_api_mcp-1.0.4}/substack_api_mcp/scripting.py +0 -0
  43. {substack_api_mcp-1.0.2 → substack_api_mcp-1.0.4}/substack_api_mcp/session.py +0 -0
  44. {substack_api_mcp-1.0.2 → substack_api_mcp-1.0.4}/substack_api_mcp/substack/__init__.py +0 -0
  45. {substack_api_mcp-1.0.2 → substack_api_mcp-1.0.4}/substack_api_mcp/substack/auth.py +0 -0
  46. {substack_api_mcp-1.0.2 → substack_api_mcp-1.0.4}/substack_api_mcp/substack/client.py +0 -0
  47. {substack_api_mcp-1.0.2 → substack_api_mcp-1.0.4}/substack_api_mcp/substack/feed.py +0 -0
  48. {substack_api_mcp-1.0.2 → substack_api_mcp-1.0.4}/substack_api_mcp/substack/markdown.py +0 -0
  49. {substack_api_mcp-1.0.2 → substack_api_mcp-1.0.4}/substack_api_mcp/substack/publishing.py +0 -0
  50. {substack_api_mcp-1.0.2 → substack_api_mcp-1.0.4}/substack_api_mcp/substack/subscribers.py +0 -0
  51. {substack_api_mcp-1.0.2 → substack_api_mcp-1.0.4}/substack_api_mcp/substack/tools/__init__.py +0 -0
  52. {substack_api_mcp-1.0.2 → substack_api_mcp-1.0.4}/substack_api_mcp/substack/tools/add_subscriber.py +0 -0
  53. {substack_api_mcp-1.0.2 → substack_api_mcp-1.0.4}/substack_api_mcp/substack/tools/cancel_scheduled_post.py +0 -0
  54. {substack_api_mcp-1.0.2 → substack_api_mcp-1.0.4}/substack_api_mcp/substack/tools/create_draft.py +0 -0
  55. {substack_api_mcp-1.0.2 → substack_api_mcp-1.0.4}/substack_api_mcp/substack/tools/create_post_tag.py +0 -0
  56. {substack_api_mcp-1.0.2 → substack_api_mcp-1.0.4}/substack_api_mcp/substack/tools/create_subscriber_tag.py +0 -0
  57. {substack_api_mcp-1.0.2 → substack_api_mcp-1.0.4}/substack_api_mcp/substack/tools/delete_comment.py +0 -0
  58. {substack_api_mcp-1.0.2 → substack_api_mcp-1.0.4}/substack_api_mcp/substack/tools/delete_post.py +0 -0
  59. {substack_api_mcp-1.0.2 → substack_api_mcp-1.0.4}/substack_api_mcp/substack/tools/duplicate_post.py +0 -0
  60. {substack_api_mcp-1.0.2 → substack_api_mcp-1.0.4}/substack_api_mcp/substack/tools/export_subscribers.py +0 -0
  61. {substack_api_mcp-1.0.2 → substack_api_mcp-1.0.4}/substack_api_mcp/substack/tools/follow_user.py +0 -0
  62. {substack_api_mcp-1.0.2 → substack_api_mcp-1.0.4}/substack_api_mcp/substack/tools/get_audience_insights.py +0 -0
  63. {substack_api_mcp-1.0.2 → substack_api_mcp-1.0.4}/substack_api_mcp/substack/tools/get_conversation.py +0 -0
  64. {substack_api_mcp-1.0.2 → substack_api_mcp-1.0.4}/substack_api_mcp/substack/tools/get_daily_stats.py +0 -0
  65. {substack_api_mcp-1.0.2 → substack_api_mcp-1.0.4}/substack_api_mcp/substack/tools/get_feed.py +0 -0
  66. {substack_api_mcp-1.0.2 → substack_api_mcp-1.0.4}/substack_api_mcp/substack/tools/get_post.py +0 -0
  67. {substack_api_mcp-1.0.2 → substack_api_mcp-1.0.4}/substack_api_mcp/substack/tools/get_post_engagement.py +0 -0
  68. {substack_api_mcp-1.0.2 → substack_api_mcp-1.0.4}/substack_api_mcp/substack/tools/get_post_stats.py +0 -0
  69. {substack_api_mcp-1.0.2 → substack_api_mcp-1.0.4}/substack_api_mcp/substack/tools/get_post_traffic.py +0 -0
  70. {substack_api_mcp-1.0.2 → substack_api_mcp-1.0.4}/substack_api_mcp/substack/tools/get_publication_stats.py +0 -0
  71. {substack_api_mcp-1.0.2 → substack_api_mcp-1.0.4}/substack_api_mcp/substack/tools/get_unread_counts.py +0 -0
  72. {substack_api_mcp-1.0.2 → substack_api_mcp-1.0.4}/substack_api_mcp/substack/tools/like_comment.py +0 -0
  73. {substack_api_mcp-1.0.2 → substack_api_mcp-1.0.4}/substack_api_mcp/substack/tools/like_post.py +0 -0
  74. {substack_api_mcp-1.0.2 → substack_api_mcp-1.0.4}/substack_api_mcp/substack/tools/list_activity.py +0 -0
  75. {substack_api_mcp-1.0.2 → substack_api_mcp-1.0.4}/substack_api_mcp/substack/tools/list_comments.py +0 -0
  76. {substack_api_mcp-1.0.2 → substack_api_mcp-1.0.4}/substack_api_mcp/substack/tools/list_conversations.py +0 -0
  77. {substack_api_mcp-1.0.2 → substack_api_mcp-1.0.4}/substack_api_mcp/substack/tools/list_note_replies.py +0 -0
  78. {substack_api_mcp-1.0.2 → substack_api_mcp-1.0.4}/substack_api_mcp/substack/tools/list_notes.py +0 -0
  79. {substack_api_mcp-1.0.2 → substack_api_mcp-1.0.4}/substack_api_mcp/substack/tools/list_post_tags.py +0 -0
  80. {substack_api_mcp-1.0.2 → substack_api_mcp-1.0.4}/substack_api_mcp/substack/tools/list_posts.py +0 -0
  81. {substack_api_mcp-1.0.2 → substack_api_mcp-1.0.4}/substack_api_mcp/substack/tools/list_recommendations.py +0 -0
  82. {substack_api_mcp-1.0.2 → substack_api_mcp-1.0.4}/substack_api_mcp/substack/tools/list_sections.py +0 -0
  83. {substack_api_mcp-1.0.2 → substack_api_mcp-1.0.4}/substack_api_mcp/substack/tools/list_subscriber_tags.py +0 -0
  84. {substack_api_mcp-1.0.2 → substack_api_mcp-1.0.4}/substack_api_mcp/substack/tools/list_subscribers.py +0 -0
  85. {substack_api_mcp-1.0.2 → substack_api_mcp-1.0.4}/substack_api_mcp/substack/tools/list_subscriptions.py +0 -0
  86. {substack_api_mcp-1.0.2 → substack_api_mcp-1.0.4}/substack_api_mcp/substack/tools/list_traffic_sources.py +0 -0
  87. {substack_api_mcp-1.0.2 → substack_api_mcp-1.0.4}/substack_api_mcp/substack/tools/pin_comment.py +0 -0
  88. {substack_api_mcp-1.0.2 → substack_api_mcp-1.0.4}/substack_api_mcp/substack/tools/publish_note.py +0 -0
  89. {substack_api_mcp-1.0.2 → substack_api_mcp-1.0.4}/substack_api_mcp/substack/tools/publish_post.py +0 -0
  90. {substack_api_mcp-1.0.2 → substack_api_mcp-1.0.4}/substack_api_mcp/substack/tools/recommend_publication.py +0 -0
  91. {substack_api_mcp-1.0.2 → substack_api_mcp-1.0.4}/substack_api_mcp/substack/tools/remove_comment.py +0 -0
  92. {substack_api_mcp-1.0.2 → substack_api_mcp-1.0.4}/substack_api_mcp/substack/tools/remove_subscriber.py +0 -0
  93. {substack_api_mcp-1.0.2 → substack_api_mcp-1.0.4}/substack_api_mcp/substack/tools/reply_to_comment.py +0 -0
  94. {substack_api_mcp-1.0.2 → substack_api_mcp-1.0.4}/substack_api_mcp/substack/tools/reply_to_note.py +0 -0
  95. {substack_api_mcp-1.0.2 → substack_api_mcp-1.0.4}/substack_api_mcp/substack/tools/schedule_post.py +0 -0
  96. {substack_api_mcp-1.0.2 → substack_api_mcp-1.0.4}/substack_api_mcp/substack/tools/search_substack.py +0 -0
  97. {substack_api_mcp-1.0.2 → substack_api_mcp-1.0.4}/substack_api_mcp/substack/tools/send_message.py +0 -0
  98. {substack_api_mcp-1.0.2 → substack_api_mcp-1.0.4}/substack_api_mcp/substack/tools/tag_post.py +0 -0
  99. {substack_api_mcp-1.0.2 → substack_api_mcp-1.0.4}/substack_api_mcp/substack/tools/tag_subscribers.py +0 -0
  100. {substack_api_mcp-1.0.2 → substack_api_mcp-1.0.4}/substack_api_mcp/substack/tools/unpublish_post.py +0 -0
  101. {substack_api_mcp-1.0.2 → substack_api_mcp-1.0.4}/substack_api_mcp/substack/tools/update_draft.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: substack-api-mcp
3
- Version: 1.0.2
3
+ Version: 1.0.4
4
4
  Summary: Self-hosted Substack API and MCP server on your own Chrome session. Noncommercial use (PolyForm Noncommercial 1.0.0).
5
5
  Project-URL: Homepage, https://cotlet.dev/integrations/substack-api-mcp-server
6
6
  Project-URL: Source, https://github.com/cotletdev/substack-api-mcp-server
@@ -21,27 +21,28 @@ Description-Content-Type: text/markdown
21
21
 
22
22
  # Substack API and MCP server, self-hosted
23
23
 
24
+ <!-- mcp-name: dev.cotlet/substack -->
25
+
24
26
  A **Substack API** and a **Substack MCP server** you run on your own computer, in Python, on the session of your own Google Chrome. All 50 Substack tools we run in production at [Cotlet](https://cotlet.dev/integrations/substack-api-mcp-server): posts and drafts from Markdown, publishing and scheduling, Notes, comments, subscribers, stats, recommendations and direct messages.
25
27
 
26
28
  Substack has no public API to write posts, publish Notes or manage subscribers, and no official MCP server. This repo gives you both:
27
29
 
28
30
  - **MCP server** for Claude Code, Claude Desktop, Cursor, Codex and any MCP client, over stdio or HTTP. [Setup](#mcp-server)
29
31
  - **REST API** on `http://127.0.0.1:8787/api/v1/substack`, with OpenAPI docs at `/docs`. [Setup](#rest-api)
30
- - **Python**: `call("list_posts", status="draft")`, plus 14 ready scripts in [`examples/`](examples/).
32
+ - **Python**: `call("list_posts", status="draft")`, plus 14 ready scripts in [`examples/`](https://github.com/cotletdev/substack-api-mcp-server/blob/main/examples/).
31
33
 
32
34
  It is for technical people who want to run it themselves, **for noncommercial use only** ([license](#license)). If you want it running 24/7 on a server, kept working when Substack changes its site, for a team or a business, use the [hosted version at cotlet.dev](https://cotlet.dev/integrations/substack-api-mcp-server).
33
35
 
34
36
  [![Substack MCP server](https://img.shields.io/badge/Substack%20MCP-50%20tools-ff6719)](#all-50-tools)
35
37
  [![Python 3.11+](https://img.shields.io/badge/python-3.11%2B-141318)](#requirements)
36
- [![License: PolyForm Noncommercial 1.0.0](https://img.shields.io/badge/license-PolyForm%20Noncommercial-141318)](LICENSE)
38
+ [![License: PolyForm Noncommercial 1.0.0](https://img.shields.io/badge/license-PolyForm%20Noncommercial-141318)](https://github.com/cotletdev/substack-api-mcp-server/blob/main/LICENSE)
37
39
  [![MCP Badge](https://lobehub.com/badge/mcp/cotletdev-substack-api-mcp-server)](https://lobehub.com/mcp/cotletdev-substack-api-mcp-server)
38
40
 
39
41
  ```bash
40
- git clone https://github.com/cotletdev/substack-api-mcp-server.git && cd substack-api-mcp-server
41
- python3 -m venv .venv && . .venv/bin/activate && pip install -e .
42
+ uv tool install substack-api-mcp # or: pip install substack-api-mcp
42
43
  substack-mcp connect # reads your Substack session from Chrome
43
44
  substack-mcp call list_posts
44
- claude mcp add substack -- "$PWD/.venv/bin/substack-mcp" mcp
45
+ claude mcp add substack -- uvx substack-api-mcp mcp
45
46
  ```
46
47
 
47
48
  ## Contents
@@ -84,6 +85,17 @@ Substack's session cookie (`substack.sid`) lasts about three months. When Substa
84
85
 
85
86
  ## Install
86
87
 
88
+ From [PyPI](https://pypi.org/project/substack-api-mcp/):
89
+
90
+ ```bash
91
+ uv tool install substack-api-mcp # or: pipx install substack-api-mcp, or: pip install substack-api-mcp
92
+ substack-mcp --version
93
+ ```
94
+
95
+ MCP clients can also run it with no install: `uvx substack-api-mcp mcp`.
96
+
97
+ From source:
98
+
87
99
  ```bash
88
100
  git clone https://github.com/cotletdev/substack-api-mcp-server.git
89
101
  cd substack-api-mcp-server
@@ -95,9 +107,9 @@ substack-mcp --version
95
107
 
96
108
  With [uv](https://docs.astral.sh/uv/): `uv venv && uv pip install -e .`, then `uv run substack-mcp ...`.
97
109
 
98
- Setting it up with an AI agent (Claude Code, Codex, Cursor)? Open it in this folder and ask it to set up the repo: [`AGENTS.md`](AGENTS.md) tells it every step, and it asks you only for the steps in Chrome.
110
+ Setting it up with an AI agent (Claude Code, Codex, Cursor)? Open it in this folder and ask it to set up the repo: [`AGENTS.md`](https://github.com/cotletdev/substack-api-mcp-server/blob/main/AGENTS.md) tells it every step, and it asks you only for the steps in Chrome.
99
111
 
100
- To update: `git pull && pip install -e .`. We copy every change of the hosted Substack tools into this repo.
112
+ To update: `uv tool upgrade substack-api-mcp` (or `pip install -U substack-api-mcp`), from source `git pull && pip install -e .`. We copy every change of the hosted Substack tools into this repo.
101
113
 
102
114
  ## Connect your Chrome
103
115
 
@@ -164,9 +176,9 @@ The MCP server lists every tool with its input schema, a description written for
164
176
  ### Claude Code
165
177
 
166
178
  ```bash
167
- claude mcp add substack -- /path/to/substack-api-mcp-server/.venv/bin/substack-mcp mcp
179
+ claude mcp add substack -- uvx substack-api-mcp mcp
168
180
  # read only:
169
- claude mcp add substack -- /path/to/substack-api-mcp-server/.venv/bin/substack-mcp mcp --read-only
181
+ claude mcp add substack -- uvx substack-api-mcp mcp --read-only
170
182
  ```
171
183
 
172
184
  ### Claude Desktop
@@ -177,8 +189,8 @@ claude mcp add substack -- /path/to/substack-api-mcp-server/.venv/bin/substack-m
177
189
  {
178
190
  "mcpServers": {
179
191
  "substack": {
180
- "command": "/path/to/substack-api-mcp-server/.venv/bin/substack-mcp",
181
- "args": ["mcp"]
192
+ "command": "uvx",
193
+ "args": ["substack-api-mcp", "mcp"]
182
194
  }
183
195
  }
184
196
  }
@@ -186,7 +198,7 @@ claude mcp add substack -- /path/to/substack-api-mcp-server/.venv/bin/substack-m
186
198
 
187
199
  ### Cursor
188
200
 
189
- `~/.cursor/mcp.json`, the same block as Claude Desktop. A ready copy is in [`mcp.json`](mcp.json).
201
+ `~/.cursor/mcp.json`, the same block as Claude Desktop. A ready copy is in [`mcp.json`](https://github.com/cotletdev/substack-api-mcp-server/blob/main/mcp.json).
190
202
 
191
203
  ### Codex
192
204
 
@@ -194,8 +206,8 @@ claude mcp add substack -- /path/to/substack-api-mcp-server/.venv/bin/substack-m
194
206
 
195
207
  ```toml
196
208
  [mcp_servers.substack]
197
- command = "/path/to/substack-api-mcp-server/.venv/bin/substack-mcp"
198
- args = ["mcp"]
209
+ command = "uvx"
210
+ args = ["substack-api-mcp", "mcp"]
199
211
  ```
200
212
 
201
213
  ### Over HTTP
@@ -265,24 +277,24 @@ call("schedule_post", post_id=draft["post_id"], publish_at="2026-11-03T08:00:00-
265
277
 
266
278
  Or one tool from the shell: `substack-mcp call get_post_stats post_id=148213977`, or with a JSON object.
267
279
 
268
- Ready scripts in [`examples/`](examples/). Each prints JSON, takes `--out file.csv` or `--out file.json` where it makes sense, and `--help`:
280
+ Ready scripts in [`examples/`](https://github.com/cotletdev/substack-api-mcp-server/blob/main/examples/). Each prints JSON, takes `--out file.csv` or `--out file.json` where it makes sense, and `--help`:
269
281
 
270
282
  | Script | What it does |
271
283
  |---|---|
272
- | [`draft_from_markdown.py`](examples/draft_from_markdown.py) | A `.md` file to a draft: the first heading is the title, headings, links and lists are kept. `--publish-at` schedules it. |
273
- | [`schedule_post.py`](examples/schedule_post.py) | Schedules a draft for a set time, or `--cancel` takes it back to drafts. |
274
- | [`publish_note.py`](examples/publish_note.py) | A Note now or at a set time, `--link-latest` adds a link card to your newest post. |
275
- | [`list_posts.py`](examples/list_posts.py) | Published, draft or scheduled posts: title, audience, dates, URL. |
276
- | [`get_post.py`](examples/get_post.py) | One post saved as a Markdown file. |
277
- | [`weekly_report.py`](examples/weekly_report.py) | New and paid subscribers, cancellations, revenue, and each new post's opens and new subscribers. |
278
- | [`post_stats.py`](examples/post_stats.py) | Opens, open rate, views, likes, restacks and new subscribers for your last N posts. |
279
- | [`daily_stats.py`](examples/daily_stats.py) | Subscribers, paid subscribers, followers and views day by day. |
280
- | [`traffic_sources.py`](examples/traffic_sources.py) | Where your visitors came from, most views first. |
281
- | [`unanswered_comments.py`](examples/unanswered_comments.py) | Comments on your last N posts that you have not replied to. |
282
- | [`reply_to_comment.py`](examples/reply_to_comment.py) | A reply under one comment. |
283
- | [`list_subscribers.py`](examples/list_subscribers.py) | Subscribers by plan or join date, with activity rating. |
284
- | [`export_subscribers.py`](examples/export_subscribers.py) | The full list as the CSV that Export gives in the dashboard. |
285
- | [`add_subscribers.py`](examples/add_subscribers.py) | Adds emails from a text or CSV file as free subscribers, after asking you. |
284
+ | [`draft_from_markdown.py`](https://github.com/cotletdev/substack-api-mcp-server/blob/main/examples/draft_from_markdown.py) | A `.md` file to a draft: the first heading is the title, headings, links and lists are kept. `--publish-at` schedules it. |
285
+ | [`schedule_post.py`](https://github.com/cotletdev/substack-api-mcp-server/blob/main/examples/schedule_post.py) | Schedules a draft for a set time, or `--cancel` takes it back to drafts. |
286
+ | [`publish_note.py`](https://github.com/cotletdev/substack-api-mcp-server/blob/main/examples/publish_note.py) | A Note now or at a set time, `--link-latest` adds a link card to your newest post. |
287
+ | [`list_posts.py`](https://github.com/cotletdev/substack-api-mcp-server/blob/main/examples/list_posts.py) | Published, draft or scheduled posts: title, audience, dates, URL. |
288
+ | [`get_post.py`](https://github.com/cotletdev/substack-api-mcp-server/blob/main/examples/get_post.py) | One post saved as a Markdown file. |
289
+ | [`weekly_report.py`](https://github.com/cotletdev/substack-api-mcp-server/blob/main/examples/weekly_report.py) | New and paid subscribers, cancellations, revenue, and each new post's opens and new subscribers. |
290
+ | [`post_stats.py`](https://github.com/cotletdev/substack-api-mcp-server/blob/main/examples/post_stats.py) | Opens, open rate, views, likes, restacks and new subscribers for your last N posts. |
291
+ | [`daily_stats.py`](https://github.com/cotletdev/substack-api-mcp-server/blob/main/examples/daily_stats.py) | Subscribers, paid subscribers, followers and views day by day. |
292
+ | [`traffic_sources.py`](https://github.com/cotletdev/substack-api-mcp-server/blob/main/examples/traffic_sources.py) | Where your visitors came from, most views first. |
293
+ | [`unanswered_comments.py`](https://github.com/cotletdev/substack-api-mcp-server/blob/main/examples/unanswered_comments.py) | Comments on your last N posts that you have not replied to. |
294
+ | [`reply_to_comment.py`](https://github.com/cotletdev/substack-api-mcp-server/blob/main/examples/reply_to_comment.py) | A reply under one comment. |
295
+ | [`list_subscribers.py`](https://github.com/cotletdev/substack-api-mcp-server/blob/main/examples/list_subscribers.py) | Subscribers by plan or join date, with activity rating. |
296
+ | [`export_subscribers.py`](https://github.com/cotletdev/substack-api-mcp-server/blob/main/examples/export_subscribers.py) | The full list as the CSV that Export gives in the dashboard. |
297
+ | [`add_subscribers.py`](https://github.com/cotletdev/substack-api-mcp-server/blob/main/examples/add_subscribers.py) | Adds emails from a text or CSV file as free subscribers, after asking you. |
286
298
 
287
299
  ```bash
288
300
  python examples/draft_from_markdown.py essay.md --publish-at 2026-11-03T08:00:00-05:00
@@ -495,7 +507,7 @@ The source is public, the license is not an open source license: noncommercial u
495
507
 
496
508
  ## License
497
509
 
498
- [PolyForm Noncommercial License 1.0.0](LICENSE). In short:
510
+ [PolyForm Noncommercial License 1.0.0](https://github.com/cotletdev/substack-api-mcp-server/blob/main/LICENSE). In short:
499
511
 
500
512
  - **Allowed:** noncommercial use: you, for your own personal purposes with your own Substack account, or a charity, school, public research or government body. Changing the code, and sharing it under the same license.
501
513
  - **Not allowed:** any commercial use, your own business included; client work; selling or offering it, or a service built on it.
@@ -508,7 +520,7 @@ Earlier versions of this repo (the scripts for the hosted API, up to commit 4399
508
520
  <a href="https://cotlet.dev">
509
521
  <picture>
510
522
  <source media="(prefers-color-scheme: dark)" srcset="assets/logo-white.svg">
511
- <img src="assets/logo.svg" alt="Cotlet" width="120">
523
+ <img src="https://raw.githubusercontent.com/cotletdev/substack-api-mcp-server/main/assets/logo.svg" alt="Cotlet" width="120">
512
524
  </picture>
513
525
  </a>
514
526
  </p>
@@ -1,26 +1,27 @@
1
1
  # Substack API and MCP server, self-hosted
2
2
 
3
+ <!-- mcp-name: dev.cotlet/substack -->
4
+
3
5
  A **Substack API** and a **Substack MCP server** you run on your own computer, in Python, on the session of your own Google Chrome. All 50 Substack tools we run in production at [Cotlet](https://cotlet.dev/integrations/substack-api-mcp-server): posts and drafts from Markdown, publishing and scheduling, Notes, comments, subscribers, stats, recommendations and direct messages.
4
6
 
5
7
  Substack has no public API to write posts, publish Notes or manage subscribers, and no official MCP server. This repo gives you both:
6
8
 
7
9
  - **MCP server** for Claude Code, Claude Desktop, Cursor, Codex and any MCP client, over stdio or HTTP. [Setup](#mcp-server)
8
10
  - **REST API** on `http://127.0.0.1:8787/api/v1/substack`, with OpenAPI docs at `/docs`. [Setup](#rest-api)
9
- - **Python**: `call("list_posts", status="draft")`, plus 14 ready scripts in [`examples/`](examples/).
11
+ - **Python**: `call("list_posts", status="draft")`, plus 14 ready scripts in [`examples/`](https://github.com/cotletdev/substack-api-mcp-server/blob/main/examples/).
10
12
 
11
13
  It is for technical people who want to run it themselves, **for noncommercial use only** ([license](#license)). If you want it running 24/7 on a server, kept working when Substack changes its site, for a team or a business, use the [hosted version at cotlet.dev](https://cotlet.dev/integrations/substack-api-mcp-server).
12
14
 
13
15
  [![Substack MCP server](https://img.shields.io/badge/Substack%20MCP-50%20tools-ff6719)](#all-50-tools)
14
16
  [![Python 3.11+](https://img.shields.io/badge/python-3.11%2B-141318)](#requirements)
15
- [![License: PolyForm Noncommercial 1.0.0](https://img.shields.io/badge/license-PolyForm%20Noncommercial-141318)](LICENSE)
17
+ [![License: PolyForm Noncommercial 1.0.0](https://img.shields.io/badge/license-PolyForm%20Noncommercial-141318)](https://github.com/cotletdev/substack-api-mcp-server/blob/main/LICENSE)
16
18
  [![MCP Badge](https://lobehub.com/badge/mcp/cotletdev-substack-api-mcp-server)](https://lobehub.com/mcp/cotletdev-substack-api-mcp-server)
17
19
 
18
20
  ```bash
19
- git clone https://github.com/cotletdev/substack-api-mcp-server.git && cd substack-api-mcp-server
20
- python3 -m venv .venv && . .venv/bin/activate && pip install -e .
21
+ uv tool install substack-api-mcp # or: pip install substack-api-mcp
21
22
  substack-mcp connect # reads your Substack session from Chrome
22
23
  substack-mcp call list_posts
23
- claude mcp add substack -- "$PWD/.venv/bin/substack-mcp" mcp
24
+ claude mcp add substack -- uvx substack-api-mcp mcp
24
25
  ```
25
26
 
26
27
  ## Contents
@@ -63,6 +64,17 @@ Substack's session cookie (`substack.sid`) lasts about three months. When Substa
63
64
 
64
65
  ## Install
65
66
 
67
+ From [PyPI](https://pypi.org/project/substack-api-mcp/):
68
+
69
+ ```bash
70
+ uv tool install substack-api-mcp # or: pipx install substack-api-mcp, or: pip install substack-api-mcp
71
+ substack-mcp --version
72
+ ```
73
+
74
+ MCP clients can also run it with no install: `uvx substack-api-mcp mcp`.
75
+
76
+ From source:
77
+
66
78
  ```bash
67
79
  git clone https://github.com/cotletdev/substack-api-mcp-server.git
68
80
  cd substack-api-mcp-server
@@ -74,9 +86,9 @@ substack-mcp --version
74
86
 
75
87
  With [uv](https://docs.astral.sh/uv/): `uv venv && uv pip install -e .`, then `uv run substack-mcp ...`.
76
88
 
77
- Setting it up with an AI agent (Claude Code, Codex, Cursor)? Open it in this folder and ask it to set up the repo: [`AGENTS.md`](AGENTS.md) tells it every step, and it asks you only for the steps in Chrome.
89
+ Setting it up with an AI agent (Claude Code, Codex, Cursor)? Open it in this folder and ask it to set up the repo: [`AGENTS.md`](https://github.com/cotletdev/substack-api-mcp-server/blob/main/AGENTS.md) tells it every step, and it asks you only for the steps in Chrome.
78
90
 
79
- To update: `git pull && pip install -e .`. We copy every change of the hosted Substack tools into this repo.
91
+ To update: `uv tool upgrade substack-api-mcp` (or `pip install -U substack-api-mcp`), from source `git pull && pip install -e .`. We copy every change of the hosted Substack tools into this repo.
80
92
 
81
93
  ## Connect your Chrome
82
94
 
@@ -143,9 +155,9 @@ The MCP server lists every tool with its input schema, a description written for
143
155
  ### Claude Code
144
156
 
145
157
  ```bash
146
- claude mcp add substack -- /path/to/substack-api-mcp-server/.venv/bin/substack-mcp mcp
158
+ claude mcp add substack -- uvx substack-api-mcp mcp
147
159
  # read only:
148
- claude mcp add substack -- /path/to/substack-api-mcp-server/.venv/bin/substack-mcp mcp --read-only
160
+ claude mcp add substack -- uvx substack-api-mcp mcp --read-only
149
161
  ```
150
162
 
151
163
  ### Claude Desktop
@@ -156,8 +168,8 @@ claude mcp add substack -- /path/to/substack-api-mcp-server/.venv/bin/substack-m
156
168
  {
157
169
  "mcpServers": {
158
170
  "substack": {
159
- "command": "/path/to/substack-api-mcp-server/.venv/bin/substack-mcp",
160
- "args": ["mcp"]
171
+ "command": "uvx",
172
+ "args": ["substack-api-mcp", "mcp"]
161
173
  }
162
174
  }
163
175
  }
@@ -165,7 +177,7 @@ claude mcp add substack -- /path/to/substack-api-mcp-server/.venv/bin/substack-m
165
177
 
166
178
  ### Cursor
167
179
 
168
- `~/.cursor/mcp.json`, the same block as Claude Desktop. A ready copy is in [`mcp.json`](mcp.json).
180
+ `~/.cursor/mcp.json`, the same block as Claude Desktop. A ready copy is in [`mcp.json`](https://github.com/cotletdev/substack-api-mcp-server/blob/main/mcp.json).
169
181
 
170
182
  ### Codex
171
183
 
@@ -173,8 +185,8 @@ claude mcp add substack -- /path/to/substack-api-mcp-server/.venv/bin/substack-m
173
185
 
174
186
  ```toml
175
187
  [mcp_servers.substack]
176
- command = "/path/to/substack-api-mcp-server/.venv/bin/substack-mcp"
177
- args = ["mcp"]
188
+ command = "uvx"
189
+ args = ["substack-api-mcp", "mcp"]
178
190
  ```
179
191
 
180
192
  ### Over HTTP
@@ -244,24 +256,24 @@ call("schedule_post", post_id=draft["post_id"], publish_at="2026-11-03T08:00:00-
244
256
 
245
257
  Or one tool from the shell: `substack-mcp call get_post_stats post_id=148213977`, or with a JSON object.
246
258
 
247
- Ready scripts in [`examples/`](examples/). Each prints JSON, takes `--out file.csv` or `--out file.json` where it makes sense, and `--help`:
259
+ Ready scripts in [`examples/`](https://github.com/cotletdev/substack-api-mcp-server/blob/main/examples/). Each prints JSON, takes `--out file.csv` or `--out file.json` where it makes sense, and `--help`:
248
260
 
249
261
  | Script | What it does |
250
262
  |---|---|
251
- | [`draft_from_markdown.py`](examples/draft_from_markdown.py) | A `.md` file to a draft: the first heading is the title, headings, links and lists are kept. `--publish-at` schedules it. |
252
- | [`schedule_post.py`](examples/schedule_post.py) | Schedules a draft for a set time, or `--cancel` takes it back to drafts. |
253
- | [`publish_note.py`](examples/publish_note.py) | A Note now or at a set time, `--link-latest` adds a link card to your newest post. |
254
- | [`list_posts.py`](examples/list_posts.py) | Published, draft or scheduled posts: title, audience, dates, URL. |
255
- | [`get_post.py`](examples/get_post.py) | One post saved as a Markdown file. |
256
- | [`weekly_report.py`](examples/weekly_report.py) | New and paid subscribers, cancellations, revenue, and each new post's opens and new subscribers. |
257
- | [`post_stats.py`](examples/post_stats.py) | Opens, open rate, views, likes, restacks and new subscribers for your last N posts. |
258
- | [`daily_stats.py`](examples/daily_stats.py) | Subscribers, paid subscribers, followers and views day by day. |
259
- | [`traffic_sources.py`](examples/traffic_sources.py) | Where your visitors came from, most views first. |
260
- | [`unanswered_comments.py`](examples/unanswered_comments.py) | Comments on your last N posts that you have not replied to. |
261
- | [`reply_to_comment.py`](examples/reply_to_comment.py) | A reply under one comment. |
262
- | [`list_subscribers.py`](examples/list_subscribers.py) | Subscribers by plan or join date, with activity rating. |
263
- | [`export_subscribers.py`](examples/export_subscribers.py) | The full list as the CSV that Export gives in the dashboard. |
264
- | [`add_subscribers.py`](examples/add_subscribers.py) | Adds emails from a text or CSV file as free subscribers, after asking you. |
263
+ | [`draft_from_markdown.py`](https://github.com/cotletdev/substack-api-mcp-server/blob/main/examples/draft_from_markdown.py) | A `.md` file to a draft: the first heading is the title, headings, links and lists are kept. `--publish-at` schedules it. |
264
+ | [`schedule_post.py`](https://github.com/cotletdev/substack-api-mcp-server/blob/main/examples/schedule_post.py) | Schedules a draft for a set time, or `--cancel` takes it back to drafts. |
265
+ | [`publish_note.py`](https://github.com/cotletdev/substack-api-mcp-server/blob/main/examples/publish_note.py) | A Note now or at a set time, `--link-latest` adds a link card to your newest post. |
266
+ | [`list_posts.py`](https://github.com/cotletdev/substack-api-mcp-server/blob/main/examples/list_posts.py) | Published, draft or scheduled posts: title, audience, dates, URL. |
267
+ | [`get_post.py`](https://github.com/cotletdev/substack-api-mcp-server/blob/main/examples/get_post.py) | One post saved as a Markdown file. |
268
+ | [`weekly_report.py`](https://github.com/cotletdev/substack-api-mcp-server/blob/main/examples/weekly_report.py) | New and paid subscribers, cancellations, revenue, and each new post's opens and new subscribers. |
269
+ | [`post_stats.py`](https://github.com/cotletdev/substack-api-mcp-server/blob/main/examples/post_stats.py) | Opens, open rate, views, likes, restacks and new subscribers for your last N posts. |
270
+ | [`daily_stats.py`](https://github.com/cotletdev/substack-api-mcp-server/blob/main/examples/daily_stats.py) | Subscribers, paid subscribers, followers and views day by day. |
271
+ | [`traffic_sources.py`](https://github.com/cotletdev/substack-api-mcp-server/blob/main/examples/traffic_sources.py) | Where your visitors came from, most views first. |
272
+ | [`unanswered_comments.py`](https://github.com/cotletdev/substack-api-mcp-server/blob/main/examples/unanswered_comments.py) | Comments on your last N posts that you have not replied to. |
273
+ | [`reply_to_comment.py`](https://github.com/cotletdev/substack-api-mcp-server/blob/main/examples/reply_to_comment.py) | A reply under one comment. |
274
+ | [`list_subscribers.py`](https://github.com/cotletdev/substack-api-mcp-server/blob/main/examples/list_subscribers.py) | Subscribers by plan or join date, with activity rating. |
275
+ | [`export_subscribers.py`](https://github.com/cotletdev/substack-api-mcp-server/blob/main/examples/export_subscribers.py) | The full list as the CSV that Export gives in the dashboard. |
276
+ | [`add_subscribers.py`](https://github.com/cotletdev/substack-api-mcp-server/blob/main/examples/add_subscribers.py) | Adds emails from a text or CSV file as free subscribers, after asking you. |
265
277
 
266
278
  ```bash
267
279
  python examples/draft_from_markdown.py essay.md --publish-at 2026-11-03T08:00:00-05:00
@@ -474,7 +486,7 @@ The source is public, the license is not an open source license: noncommercial u
474
486
 
475
487
  ## License
476
488
 
477
- [PolyForm Noncommercial License 1.0.0](LICENSE). In short:
489
+ [PolyForm Noncommercial License 1.0.0](https://github.com/cotletdev/substack-api-mcp-server/blob/main/LICENSE). In short:
478
490
 
479
491
  - **Allowed:** noncommercial use: you, for your own personal purposes with your own Substack account, or a charity, school, public research or government body. Changing the code, and sharing it under the same license.
480
492
  - **Not allowed:** any commercial use, your own business included; client work; selling or offering it, or a service built on it.
@@ -487,7 +499,7 @@ Earlier versions of this repo (the scripts for the hosted API, up to commit 4399
487
499
  <a href="https://cotlet.dev">
488
500
  <picture>
489
501
  <source media="(prefers-color-scheme: dark)" srcset="assets/logo-white.svg">
490
- <img src="assets/logo.svg" alt="Cotlet" width="120">
502
+ <img src="https://raw.githubusercontent.com/cotletdev/substack-api-mcp-server/main/assets/logo.svg" alt="Cotlet" width="120">
491
503
  </picture>
492
504
  </a>
493
505
  </p>
@@ -0,0 +1,11 @@
1
+ {
2
+ "mcpServers": {
3
+ "substack": {
4
+ "command": "uvx",
5
+ "args": [
6
+ "substack-api-mcp",
7
+ "mcp"
8
+ ]
9
+ }
10
+ }
11
+ }
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "substack-api-mcp"
3
- version = "1.0.2"
3
+ version = "1.0.4"
4
4
  description = "Self-hosted Substack API and MCP server on your own Chrome session. Noncommercial use (PolyForm Noncommercial 1.0.0)."
5
5
  readme = "README.md"
6
6
  license = "PolyForm-Noncommercial-1.0.0"
@@ -24,6 +24,7 @@ Source = "https://github.com/cotletdev/substack-api-mcp-server"
24
24
 
25
25
  [project.scripts]
26
26
  substack-mcp = "substack_api_mcp.cli:main"
27
+ substack-api-mcp = "substack_api_mcp.cli:main"
27
28
 
28
29
  [build-system]
29
30
  requires = ["hatchling>=1.27"]
@@ -11,7 +11,7 @@ from __future__ import annotations
11
11
  import asyncio
12
12
  from typing import Any
13
13
 
14
- __version__ = "1.0.2"
14
+ __version__ = "1.0.4"
15
15
 
16
16
 
17
17
  def call(tool: str, args: dict[str, Any] | None = None, **kwargs: Any) -> Any:
@@ -1,8 +0,0 @@
1
- {
2
- "mcpServers": {
3
- "substack": {
4
- "command": "/path/to/substack-api-mcp-server/.venv/bin/substack-mcp",
5
- "args": ["mcp"]
6
- }
7
- }
8
- }