substack-mcp
Provides tools for managing a Substack publication, including writing and publishing posts, managing subscribers, reading analytics, scheduling Notes, and researching other writers.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@substack-mcpDraft this week's post from my notes in my voice."
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Substack MCP
Substack MCP server for Claude Code and AI agents. 65 tools for drafts, posts, Notes, subscribers, analytics, tags, comments, and researching other writers.
Substack has no public API, which is why your assistant cannot see any of it, and why most things that claim to connect publish posts with the HTML tags showing.
This one speaks Substack's own document format. Ask for a draft and you get a draft, with the YouTube link as a player and the paywall where you put it.
You: which of my posts actually converted free readers to paid?
Claude: Ranking your last 40 posts by paid signups.
1. "The part nobody tells you about pricing" 18 paid
2. "I audited 60 newsletters. Here is the gap" 11 paid
3. "Why your welcome email is costing you" 9 paid
All three are teardowns with a specific number in the title.
Your five worst converters are all essays with abstract titles.Built and maintained by Navid Moazzez.
Contents 📑
Section | ||
1 | Real prompts, not features | |
2 | One line, no account needed | |
3 | Getting your session cookie | |
4 | Claude, Cursor, Windsurf, the rest | |
5 | And the two things that fail | |
6 | All 65, grouped by what they reach | |
7 | What is guarded and what is not | |
8 | Markdown, embeds, paywalls | |
9 | What is stored, and where | |
10 | When something breaks | |
The questions people actually ask |
Related MCP server: Substack MCP Server
1. What you can ask it 💬
Draft this week's post from my notes, in the voice of my last five.
Which of my posts got the most paid conversions, and what do they have in common?
How many subscribers have not opened anything in 90 days?
Pull every Note that mentions pricing from the three writers I compete with.
Add a paywall after the third section of that draft.
Schedule this Note for 9am Tuesday.
Compare my open rate to what it was six months ago.
Read my inbox and tell me what my corner of Substack is arguing about this week.
Turn my last three posts into a guide, and put the YouTube version at the top.
The last one is the point. It reads your existing posts, writes a new draft in your format, and embeds the video as a real player rather than a blue link, because it speaks Substack's document format rather than pasting HTML at it.
2. Quick install ⚡
Node 20 or newer. Nothing else.
npx -y @thenavidm/substack-mcp@latest --versionThat is the whole install. npx fetches it on demand, so there is nothing to update later.
Installing the package needs no account. Only connecting it does, which is the next section.
Before you start
You need | Check with | If missing |
Node 20 or newer |
| |
A Substack publication you own | Open your publication's dashboard | Start one at substack.com, it is free |
Its canonical address | It ends | Custom domains do not serve the API, see below |
Use theyourname.substack.com address, not a custom domain. Substack does not
serve its API on custom domains: the request redirects and ends in a 404.
3. Setup 🔑
Substack has no public API and no OAuth. Everything here runs on your browser session cookie, exactly like the Substack tab you already have open.
Treat that cookie like a password. It is full access to your account. Never paste it into an issue, a gist, or a chat with anyone.
Option A: paste the cookie (fastest, recommended)
npx @thenavidm/substack-mcp@latest loginIt asks for your publication URL and the cookie, resolves your user id, and stores the result encrypted. Then you can leave the env block out of your client config entirely.
To find the cookie:
Open your publication and sign in.
DevTools, then Application, then Cookies.
Copy the value of
connect.sid. It is long and starts withs%3A.
Turn off ad blockers first. Some of them strip the cookie from that panel.
Option B: read it from the Chrome you already have open
npx @thenavidm/substack-mcp@latest login --playwriterUses Playwriter to read the cookie out of your running Chrome, where you are already signed in. No browser launch, no sign-in, no CAPTCHA. Requires Playwriter and its extension.
Option C: launch a browser and sign in
npm i -g playwright && npx playwright install chromium
npx @thenavidm/substack-mcp@latest login --playwrightA browser opens and waits up to ten minutes for you to sign in, CAPTCHA and emailed link included. This is much the slowest option, and the only one that works on a machine with no Chrome, or in CI.
Playwright is not bundled. It is large, it is only ever used by this one command, and the server never loads it, so your tool calls are not slower for it existing.
Or just use environment variables
Nothing above is required. Set SUBSTACK_PUBLICATION_URL and SUBSTACK_SESSION_TOKEN and you are done. SUBSTACK_USER_ID is optional and looked up automatically when absent.
When it expires
Sessions do expire, commonly reported at around 90 days, though I have not measured it. When calls start failing with an authentication error, run login again, or paste a fresh cookie. doctor warns you once a stored session passes 75 days.
4. Connect your client 🔌
Every block below is complete on its own. Pick your client, paste, done.
Replace example.substack.com with your publication and your-connect-sid-value with the cookie from section 3.
Claude Code
claude mcp add substack \
-e SUBSTACK_PUBLICATION_URL=example.substack.com \
-e SUBSTACK_SESSION_TOKEN=your-connect-sid-value \
-- npx -y @thenavidm/substack-mcp@latestRun /mcp inside Claude Code and substack should be listed. Remove it later with claude mcp remove substack.
Claude Desktop
Open Settings, then Developer, then Edit Config. That reveals claude_desktop_config.json. Or go straight there:
macOS |
|
Windows |
|
Linux |
|
{
"mcpServers": {
"substack": {
"command": "npx",
"args": ["-y", "@thenavidm/substack-mcp@latest"],
"env": {
"SUBSTACK_PUBLICATION_URL": "example.substack.com",
"SUBSTACK_SESSION_TOKEN": "your-connect-sid-value"
}
}
}
}If the file already has other servers, add only the "substack" block inside "mcpServers" and put a comma after the entry before it. One bad comma stops every server loading, not just this one.
Then quit Claude Desktop completely and reopen it. On macOS use Cmd+Q, closing the window is not enough. It only reads that file at startup.
Claude Desktop does not inherit your shell PATH, so ifnpx is not found, run
which npx and use that absolute path as command.
Cursor
~/.cursor/mcp.json for every project, or .cursor/mcp.json inside one. Same JSON as above. Reload the window afterwards.
Windsurf
~/.codeium/windsurf/mcp_config.json. Same JSON. Reload afterwards.
VS Code
.vscode/mcp.json in a project, or run MCP: Add Server from the command palette.
Anything else
Zed, Cline, Continue and any other MCP client over stdio all work. They each want the same three things: command, args, and env.
Docker
docker run -i --rm \
-e SUBSTACK_PUBLICATION_URL=example.substack.com \
-e SUBSTACK_SESSION_TOKEN=your-connect-sid-value \
ghcr.io/navidmoazzez/substack-mcp:latestSelf-hosted over HTTP
Only one thing needs this: schedule_note publishes from the machine the server runs on, so a Note queued for 9am fires only if that machine is awake. Everything else is fine on a laptop.
substack-mcp --http --port=8788It binds to 127.0.0.1 and serves /health. To reach it from elsewhere set SUBSTACK_MCP_HOST=0.0.0.0 and SUBSTACK_MCP_TOKEN to a random string, and put it behind TLS.
The HTTP transport holds a live credential for your Substack account. Binding it beyond localhost without a token hands your account to anyone who finds the port.
5. Check it worked 🩺
npx @thenavidm/substack-mcp@latest doctordoctor runs the checks in order and names the actual problem, rather than leaving you to guess which of six things is wrong.
Two things account for almost every failure. Node is not on the PATH your client sees, which the tip above covers. Or the session cookie is wrong or expired, which doctor names directly.
6. Tools 🛠️
65 tools. Every one declares whether it reads, writes, or does something that cannot be undone, so your client can show you the difference before anything runs.
Every publication-scoped tool takes an optional publication argument to pick which connected Substack it acts on. See section 7.
Drafts
Tool | What it does | |
| write | Create a draft from markdown. Private until you publish |
| write | Change any field. Only what you pass is touched |
| read | Read a draft, body returned as markdown you can edit |
| read | Unpublished drafts, most recently edited first |
| destructive | Permanent, no trash. Needs |
| destructive | Publishes and emails your list. Needs |
| write | Schedule on Substack's side, so it fires without you |
| write | Back to a plain draft |
| read | What is queued, soonest first |
| write | Replace the body with a document you build node by node |
| read | See what a body will render as, changing nothing |
| read | Section ids, which |
create_draft takes title, subtitle, body, body_format, section_id, audience (everyone, only_free, only_paid, founding), type (newsletter, podcast, thread), cover_image, and three SEO fields.
get_draft takes body_format of markdown, prosemirror, or both. Markdown is the default, because it is what you can actually edit and send back.
Posts
Tool | What it does | |
| read | Published posts, newest first |
| read | Read a post by slug, from any publication |
| read | Same, by numeric id |
| read | Search your own posts by keyword |
| read | Opens, clicks, views, signups, reactions for one post |
| read | Rank posts by any metric, to find what worked |
Notes
Notes have no draft state on Substack. Writing one publishes it, immediately and publicly.
Tool | What it does | |
| destructive | Live and public at once. Needs |
| destructive | With a link preview card. Needs |
| write | Queue one for later. See the caveat below |
| read | What is queued, published, failed or cancelled |
| write | Cancel before it fires |
| read | Notes you have published |
| destructive | Permanent. Needs |
Substack does not schedule Notes, so the queue is kept locally and this server publishes each one when it comes due.
That only happens while the server is running. A Note set for 9am fires at 9am if your machine is awake with your client open. Otherwise it goes out on the next start after that time, flagged as published late.
Nothing is ever dropped. For scheduling that does not depend on your laptop, see self-hosted over HTTP.
Subscribers
Tool | What it does | |
| read | Filter on 48 columns with 18 operators |
| read | The only way to actually read engagement metrics |
| read | Totals, free and paid |
| write | Add an address to the list |
list_subscribers takes filters as {column, operator, value} combined with AND, plus search, sort_by, sort_direction, limit and offset.
Which operators apply depends on the column's type:
Type | Operators |
|
|
|
|
|
|
|
|
|
|
The 48 columns cover identity (name, email, country, state, group), subscription (type, dates, revenue, Stripe plan, attribution), email engagement (opens over 7d/30d/6mo, links clicked, sections) and site engagement (views, comments, shares, days active, activity rating). The full list with types reaches your client in the tool's schema, so the model does not have to guess.
There is no OR and no nesting. That is a limit of Substack's endpoint. Anything needing OR has to be issued as separate calls.
Two things about export_subscribers, both verified against the live API:
tag_idsandgroup_membershipcannot be exported. Substack drops them without failing, so they come back inmissing_columns. Asking for all 48 returns 46.Values arrive display-formatted. Revenue is
"$50.00"here and the number50throughlist_subscribers.
Analytics
Tool | What it does | |
| read | One of 16 reports, listed below |
| read | The headline numbers |
| read | Delivery, opens, clicks |
| read | Plans, prices, what each brings in |
get_analytics reports: unsubscribes, unsubscribes_timeseries, retention, retention_summary, referrals_leaderboard, referrals_summary, audience_overlap, audience_locations, subscriber_notes, paid_subscriber_growth, arr_timeseries, followers_timeseries, subscribers_timeseries, growth_sources, growth_events, network_attribution.
audience_overlap is the interesting one. It names the other Substacks whose readers overlap yours, with percentages, which is the list of people worth doing a swap with.
Tags and comments
Tool | What it does | |
| read | Every tag on the publication |
| write | Create one |
| read | Tags on one post |
| write | Tag a post |
| destructive | Untag. Needs |
| read | Comments on your post |
| destructive | Public immediately. Needs |
| destructive | Permanent. Needs |
Reading Substack
Tool | What it does | |
| read | What this account subscribes to |
| read | Your inbox |
| read | Any post you have access to, including paid ones |
| read | The Notes timeline |
| read | Everything one account has published |
| read | A Note and its replies |
| destructive | Republishes to your followers. Needs |
Publication
Tool | What it does | |
| read | Every setting on the settings page |
| write | Change them, including theme colours |
| read | Which account is connected |
| read | Find Substacks by name or topic. No auth needed |
| read | Public details of any publication |
| read | Who can write on it, with byline ids |
| read | Result of the last subscriber import |
update_publication_settings names accent_color and color_links explicitly, because Substack stores them under opaque theme variable names, and color_links being off is the usual reason links render nearly invisible on a dark theme.
Templates
Tool | What it does | |
| read | Your saved post templates |
| write | Save one |
| destructive | Permanent. Needs |
| write | Start a draft from one, formatting exact |
Research
Tool | What it does | |
| read | Another writer's posts with engagement numbers |
| read | Their Notes, ranked by likes or restacks |
| read | Up to 10 publications ranked together |
| read | Any public post from its URL |
compare_publications scores by likes, plus comments times two, plus restacks times three, because a comment and a restack both cost more effort than a like.
Images
Tool | What it does | |
| write | Upload from a URL or a local file, get a CDN URL back |
Takes exactly one of url or path. PNG, JPEG, GIF and WebP up to 10MB. The type is checked from the file's contents, not its extension.
Resources and prompts
Beyond tools, the server exposes two MCP resources (substack://publication and substack://connected) so a client can load your publication's context without spending a tool call, and four prompts: Draft a post from an idea, Find what worked, Study another writer, and Find lapsed subscribers.
Several publications
One Substack login often owns more than one publication.
"env": {
"SUBSTACK_PUBLICATIONS": "[{\"publication_url\":\"one.substack.com\",\"session_token\":\"...\"},{\"publication_url\":\"two.substack.com\",\"session_token\":\"...\"}]"
}Every publication-scoped tool takes an optional publication argument, matched loosely against the hostname, so "two" finds two.substack.com. Leave it out and the first one is used.
Ask for a publication that is not connected and the error names the ones that are, rather than failing silently against the wrong Substack.
7. Writing safely 🔒
Two positions are common and both are wrong. Ship publish and delete unguarded, and one mis-parsed instruction emails your entire list. Remove them and call that safety, and you have not made anything safer, you have moved the work back to the human.
The actual hazard is narrow and worth naming.
publish_draft with send: true emails every subscriber you have, and there is no unsend. delete_draft has no undo. publish_note and comment_on_post are public the instant they run.
None of these is dangerous when a person meant it. All of them are dangerous one plausible misreading of "tidy up my drafts" away.
So everything works, and the irreversible things need an explicit confirm: true:
delete_draft is irreversible: permanently delete draft 4821.
Nothing has been changed. Re-run with confirm: true if that is what you want.A careless call trips over that. An intentional one clears it in a single retry.
Turning writes off entirely
"env": { "SUBSTACK_READ_ONLY": "1" }Drops the server to its 41 read tools. Write tools are not merely refused, they are not advertised, so the model never tries.
SUBSTACK_ALLOW_DESTRUCTIVE=0 is the middle setting: drafting and tagging still work, publishing and deleting do not.
Annotations
Every tool sets readOnlyHint, destructiveHint, idempotentHint and openWorldHint explicitly. MCP defaults destructiveHint and openWorldHint to true when omitted, so a read tool left unannotated shows up in a client as dangerous, which trains people to ignore the warnings that matter.
An audit log
"env": { "SUBSTACK_AUDIT_LOG": "/Users/you/.substack-mcp/audit.log" }Append-only, one JSON line per attempted write, allowed or blocked.
Prompt injection
Several tools return text other people wrote. Comments, your reader feed, another writer's posts.
An agent that can read that text and also publish is exposed to instructions hidden inside it. Someone can leave a comment that reads like a command.
Two things push back on that. Every one of those tools says so in its own response, and the server's instructions tell the model to treat that text as data rather than orders.
Neither is complete. The real defence for an agent working unattended is SUBSTACK_READ_ONLY=1, which removes the write tools entirely.
Risks worth knowing
This uses an undocumented API.
Substack publishes no REST API and no OAuth. These are the endpoints its own web app calls.
They can change without notice, and when they do, tools break until the fix ships.
Your session cookie is full account access.
Anyone who gets it can post as you, read your subscribers, and change your billing. It is exactly as sensitive as your password.
Never paste it into an issue.
An agent with publish rights can email your entire list.
The confirmation gate makes that hard to do by accident. It does not make it impossible for a determined bad instruction.
If you are pointing an autonomous agent at this, run it with SUBSTACK_READ_ONLY=1.
Automated subscriber additions are how publications get marked as spam.
add_subscriber exists for people who asked to be added. Importing anyone else is your problem, not Substack's.
Terms of service.
Automating your own account through its own web endpoints is not something Substack documents or blesses.
I am not aware of anyone being banned for it. I cannot promise it, and neither can anyone else shipping a tool like this.
8. Writing posts ✍️
draft_body is not HTML. It is a JSON ProseMirror document. This is the single most common way a Substack integration goes wrong: send HTML and the API returns 200, then the post renders with the tags visible as literal text. There is no error. You find out by looking at the published post.
This server converts markdown, HTML, or a ready-made document into the real format, and detects which you sent.
Markdown
Headings, bold, italic, inline code, strikethrough, links, images, nested lists to any depth with ordered and unordered mixed, fenced code blocks with a language, blockquotes and horizontal rules.
Embeds
A line containing only a YouTube, X, Spotify or Vimeo URL becomes a real embedded player:
Here is the walkthrough.
https://www.youtube.com/watch?v=dQw4w9WgXcQ
The transcript is below.Substack's editor does this client-side, so it never happens for anything written through the API. Doing it here is why a draft from this server looks like one you made by hand. Put the URL inside a sentence and it stays an ordinary link.
x.com links are rewritten to twitter.com, because Substack's embed only resolves the latter.
Paywalls
The free part everyone sees.
<paywall>
The part only paying subscribers get.Tables
Substack's document format has no table node, so a table cannot be rendered natively. Rather than mangle the pipes into a paragraph, a markdown table is preserved verbatim in a code block. The content survives and you can reformat it in the editor.
Reading it back
get_draft returns markdown by default. Edit one sentence, send it back to update_draft, and the rest of the formatting survives.
preview_draft_body shows exactly what a body will produce, including how many embeds were created and whether the paywall registered, without touching anything.
9. Your data 💾
Nothing is sent anywhere except Substack. There is no telemetry, no analytics, and no third-party service in the path.
Two files, both in ~/.substack-mcp (SUBSTACK_MCP_HOME moves it):
session.json, only if you ran login. Written 0600, encrypted with AES-256-GCM under a key derived from this OS account and this machine, which is never stored.
Be clear about what that buys. A copied file is useless elsewhere, and a casual disk or backup read sees ciphertext.
It is machine binding, not a vault. Code running as you on this machine can re-derive the key.
That is the same exposure as the environment variable path, which is why environment variables stay fully supported.
scheduled-notes.json, the local queue for schedule_note. Plain JSON, 0600, containing the text of Notes you have not published yet.
Your posts, drafts and subscribers are never copied locally. Every read goes to Substack live.
How it works
your MCP client
| stdio (or HTTP)
v
substack-mcp
|
+-- write guard confirm, read-only, audit log
+-- content pipeline markdown/HTML <-> ProseMirror, embeds
+-- one HTTP client timeout, retry, backoff, spacing
|
v
Substack's own JSON endpoints, signed with your session cookieEvery request goes through one client, so an upstream change is fixed in one file. That client adds four things a bare fetch does not:
A real deadline. Node applies no request timeout, only a 10 second connect timeout. A host that accepts the connection and then goes quiet would otherwise hang a tool call forever. Default 30 seconds, set with SUBSTACK_REQUEST_TIMEOUT_MS.
Retries that help. 429 and 5xx get exponential backoff with jitter, honouring Retry-After. Nothing else is retried, because nothing else resolves by waiting.
Request spacing. A floor of 350ms between requests, serialised through a queue, so a model looping over 200 posts does not get your account rate limited. SUBSTACK_MIN_REQUEST_INTERVAL_MS.
A browser identity. The client sends a browser User-Agent, Referer and Origin, because some publications sit behind Cloudflare, which blocks unrecognized clients. The error mapper recognises a Cloudflare block hiding inside a 403 and tells you to use the canonical host.
Errors map to typed classes, so the message names the fix rather than saying "Substack API error":
Class | Status | Cause |
| 401/403 | Expired session, or a Cloudflare block |
| 429 | Too many requests, after retries |
| 400 | Bad arguments |
| 404 | No such draft, post or note |
| 5xx | Substack's problem |
| 408 | Our own deadline, no response arrived |
10. Troubleshooting 🔧
Run npx @thenavidm/substack-mcp@latest doctor first. It checks credentials, config, connectivity and byline resolution, and names what is wrong.
"Substack rejected the session" Your cookie expired. Get a fresh connect.sid, or run login again.
Calls fail on a custom domain A publication served on its own domain does not answer the API there. The request redirects and ends in a 404. Set SUBSTACK_PUBLICATION_URL to the canonical *.substack.com host instead, which is served directly. The research tools retry the canonical host automatically; everything else needs it configured.
Some custom domains sit behind Cloudflare, which can answer 403 with error code: 1010. That is the same fix.
Post renders with visible HTML tags You are on an older version, or something else wrote that draft. This server never sends HTML as draft_body. Check with preview_draft_body.
create_draft fails on the byline Substack requires a byline, and it is your numeric user id. Normally resolved automatically. If that fails, set SUBSTACK_USER_ID. doctor tells you which.
A scheduled Note did not fire The server was not running at the time. It publishes on the next start, marked published_late. Check with list_scheduled_notes, and see the next section.
Tools missing from the list SUBSTACK_READ_ONLY is set. The 24 write tools are hidden by design.
Nothing happens at all Check your client's MCP logs. On a bad config the server still starts and reports the problem per tool call, rather than failing silently at boot.
Environment variables
Variable | Default | What it does |
| Your publication, e.g. | |
| The | |
| resolved | Your numeric user id |
| JSON array, for several publications | |
|
| Disable every write |
|
| Allow publish and delete |
| Append-only log of attempted writes | |
|
| Per-request deadline |
|
| Minimum spacing between requests |
|
| Retries on 429 and 5xx |
| Chrome | Override the browser signature |
|
| Where session and queue live |
|
| HTTP bind address |
|
| HTTP port |
| Bearer token for HTTP | |
| Extra origins beyond localhost |
Environment variables
Variable | Default | What it does |
| Your publication, e.g. | |
| The | |
| resolved | Your numeric user id |
| JSON array, for several publications | |
|
| Disable every write |
|
| Allow publish and delete |
| Append-only log of attempted writes | |
|
| Per-request deadline |
|
| Minimum spacing between requests |
|
| Retries on 429 and 5xx |
| Chrome | Override the browser signature |
|
| Where session and queue live |
|
| HTTP bind address |
|
| HTTP port |
| Bearer token for HTTP | |
| Extra origins beyond localhost |
FAQ ❓
An MCP server is a standard way to give an AI assistant real access to a tool.
Instead of describing your Substack to Claude and hoping it guesses right, the server exposes your actual drafts, subscribers and analytics as things the assistant can read and act on.
MCP is the protocol they agree on, so one server works in Claude, Cursor, Windsurf and anything else that speaks it.
Substack is a publishing platform for newsletters and blogs. Writers publish posts that go out by email and live on the web, sell paid subscriptions, and post short updates called Notes. This server connects an AI assistant to a Substack publication you own.
You need to be able to paste a line into a terminal and copy a value out of your browser. That is the whole skill requirement. Section 3 walks through the browser part click by click, and doctor tells you what is wrong in plain language if something does not work.
Nothing goes anywhere except Substack. There is no backend, no telemetry, and no third party in the path. Your session cookie and any queued Notes sit in ~/.substack-mcp on your own machine, and section 7 says exactly what is in each file.
Three things the dashboard cannot do.
It reads the engagement metrics Substack lets you filter on but never shows you, through export_subscribers.
It pulls another writer's posts and Notes with their like and restack counts, so you can rank by what actually worked.
And it turns markdown into real Substack formatting, including embeds and paywalls, which the editor does not do for anything written outside it.
Not without being told twice. delete_draft, delete_note, delete_comment and delete_template are permanent, with no trash to recover from.
All four refuse to run unless the call passes confirm: true. The same guard covers publish_draft, because publishing with send: true emails your whole list and an email cannot be unsent.
Setting SUBSTACK_READ_ONLY=1 removes all 24 write tools from the list entirely.
It costs nothing. The server is MIT licensed and free, and it talks to your existing Substack account. You do not need a paid Substack plan, though some analytics reports only return data if your publication has paid subscribers.
It works with any client that speaks MCP. Claude Code, Claude Desktop, Cursor, Windsurf, VS Code, Zed and Cline are all covered in section 2, and anything else that supports MCP over stdio will work with the same three settings.
You can connect as many as you like. One Substack login often owns several, so every publication-scoped tool takes an optional publication argument matched against the hostname. Set SUBSTACK_PUBLICATIONS to a JSON array and pass publication: "example" to pick one, or leave it out and the first is used.
Substack sessions do expire, and when yours does every authenticated tool starts returning an authentication error naming the cause. The fix is to grab a fresh connect.sid cookie and update it, or run substack-mcp login again. doctor warns you once a stored session passes 75 days, before it breaks.
Remove the server from your client's config, which for Claude Code is claude mcp remove substack. Then delete ~/.substack-mcp to remove the stored session and any queued Notes. Nothing is left behind, and nothing was ever stored anywhere but your own machine.
Questions
Run into a problem or have a question? Open an issue and I will help.
About the author 👋
Navid Moazzez is a leading AI business strategist, and the host of the AI Creator Summit, watched by 100,000+ creators. He helps creators and founders master AI and build their own AI Operating System (AI OS) to automate their business and life. This Substack MCP server is one piece of that system.
Links
Personal website: navid.me
Store: navid.bio
Navid Media: navid.media
YouTube: @thenavidm and @thenavidai
X: @thenavidm
Instagram: @thenavidm
LinkedIn: thenavidm
If this is useful, star the repo and come say hi on X.
Dependencies 📦
Library | License | What it does |
MIT | The MCP server and transports | |
MIT | Tool argument schemas and validation |
Playwright is an optional peer dependency, used only by login --playwright and never loaded by the server.
Security 🛡️
Found a vulnerability? Report it privately, not as a public issue. SECURITY.md covers what this server holds, the write-safety model, and running it over HTTP.
License ⚖️
MIT. Free to use, modify, and share.
Not affiliated with, endorsed by, or connected to Substack Inc.
© 2026 NM Media. Made with ❤️ by Navid Moazzez.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Connectors
SEO research, audits, backlinks, GSC, and content workflow tools for AI agents.
Give your AI agents the tools to build, manage, and run automation workflows.
SEO & marketing toolkit for AI agents: GA4, Search Console, AdSense, GTM, PageSpeed, Trends.
Connect AI agents to 1000+ apps with managed authentication and tool-calling.
Related MCP Servers
- AlicenseBqualityNot gradedmaintenanceEnables interaction with Substack publications through natural conversation, allowing users to create posts with cover images, publish notes, manage content, and retrieve profile information.82
- AlicenseAqualityFmaintenanceEnables AI tools to programmatically manage Substack content, including creating drafts, publishing posts, and posting to Substack Notes. It supports image uploads, live blogging, and document formatting compatible with Substack's ProseMirror editor.119MIT
- AlicenseNot gradedqualityDmaintenanceEnables programmatic management of Substack content, including creating drafts, publishing posts, and uploading images. It supports specialized features like live blogging and posting to Substack Notes through MCP-compatible AI tools.MIT
- AlicenseAqualityAmaintenanceEnables LLM clients to interact with Substack's API for automations like creating posts and managing drafts.2793970MIT
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/navidmoazzez/substack-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server