substack-mcp
You can manage Substack drafts, images, publishing, scheduling, and Notes via MCP tools.
Create and update drafts from Markdown with title, subtitle, and audience.
Upload local or remote images to Substack’s CDN.
Set a draft’s cover/thumbnail image.
Publish a draft immediately, optionally emailing subscribers and auto-sharing.
Schedule or unschedule future publishing.
List, get, and delete drafts.
Post short-form Notes to Substack’s public feed (up to 4000 chars).
Allows creating, editing, scheduling, and publishing drafts on a Substack publication, including uploading images and setting cover thumbnails.
Click on "Deploy 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-mcp create a draft called 'My First Post' and schedule it for tomorrow"
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
A Model Context Protocol (MCP) server for Substack. Lets Claude Code create drafts, upload images, set cover thumbnails, schedule, and publish posts on your Substack publication.
Built on top of
python-substack. Uses Substack's internal API (no public posting API exists). Not affiliated with Substack Inc.
Tools
Required
create_draft(title, content_markdown, subtitle?, audience?)— Create a new draft from Markdown.update_draft(post_id, title?, subtitle?, content_markdown?, audience?)— Edit an existing draft.upload_image(image_path)— Upload a local file or remote URL to Substack's CDN, returning the URL.publish_draft(post_id, send_email?, share_automatically?)— Publish immediately.send_emailtoggles email delivery.
Recommended
schedule_draft(post_id, iso_datetime)— Schedule a publish for a future date/time (ISO 8601).unschedule_draft(post_id)— Cancel a scheduled publish.set_cover_image(post_id, image_url)— Set the cover thumbnail (fromupload_imageURL).
Utility
list_drafts(limit?)— List recent drafts.get_draft(post_id)— Get a draft's full body.delete_draft(post_id)— Permanent deletion.
Related MCP server: Substack MCP Server
Setup
# 1. Install dependencies
uv pip install -e .
# 2. Make sure you're logged in to Substack in Chrome (or Brave/Edge) — that's it.
# 3. Save credentials — auto-detects your existing browser session
substack-mcp-setup
# 4. Register with Claude Code
claude mcp add substack-mcp --scope user -- /Users/$USER/substack/.venv/bin/substack-mcpRestart Claude Code, then /mcp should show substack-mcp as connected.
Run fully in the cloud with Cloudflare
The Cloudflare deployment exposes a stable, OAuth 2.1-protected Streamable HTTP
endpoint at https://<worker>.workers.dev/mcp. ChatGPT performs the writing and
reasoning; the Worker and its on-demand Python Container only execute Substack
operations. No OpenAI API key, Secure MCP Tunnel, or always-on computer is
required.
Requirements:
a Cloudflare account with Workers Containers enabled
Node.js 22.18 or newer
a GitHub OAuth App used only to verify who may connect
a current
substack.sidsession token (store it as a Cloudflare secret)
Install and create the OAuth state store:
npm install
npx wrangler login
npx wrangler kv namespace create OAUTH_KVPut the returned namespace ID in wrangler.jsonc in place of
REPLACE_WITH_OAUTH_KV_ID. Your final Worker URL will normally be:
https://substack-mcp.<your-workers-subdomain>.workers.devCreate a GitHub OAuth App with that URL as its homepage and
https://substack-mcp.<your-workers-subdomain>.workers.dev/callback as its
authorization callback URL. Then store all private values using Wrangler's
interactive secret prompt (never commit them or paste them into chat):
npx wrangler secret put GITHUB_CLIENT_ID
npx wrangler secret put GITHUB_CLIENT_SECRET
npx wrangler secret put ALLOWED_GITHUB_LOGINS
npx wrangler secret put SUBSTACK_PUBLICATION_URL
npx wrangler secret put SUBSTACK_SESSION_TOKEN
npm run deployALLOWED_GITHUB_LOGINS is a comma-separated allowlist, for example
nanameru. SUBSTACK_PUBLICATION_URL is the full publication URL. For the
session token, copy only the value of the substack.sid cookie from a browser
that is logged in to Substack. The browser is needed once for setup; it does not
need to remain open afterward. Signing out of all Substack sessions invalidates
this credential, so update the Cloudflare secret after doing that.
In ChatGPT developer mode, create an app/connector from:
https://substack-mcp.<your-workers-subdomain>.workers.dev/mcpChoose OAuth when prompted and sign in with an allowlisted GitHub account.
Draft creation is non-public. Publishing, subscriber email, draft deletion, and
Notes remain confirmation-gated by the tool descriptions and the bundled
substack-article skill.
Connect to ChatGPT Work with Secure MCP Tunnel
This server uses the local substack.sid browser session and should not be
published directly on the internet. For a private ChatGPT Work connection, use
OpenAI Secure MCP Tunnel.
The tunnel keeps the stdio MCP and Substack credential on your Mac while making
the tools available to an authorized ChatGPT workspace.
Prerequisites:
macOS with Chrome, Brave, Edge, Chromium, Vivaldi, or Opera already signed in to Substack
ChatGPT developer mode enabled
an OpenAI Platform tunnel ID associated with the target ChatGPT workspace
tunnel-clientinstalled from OpenAI Platform tunnel settingsa tunnel runtime API key with Tunnels Read + Use permission
Run:
git clone https://github.com/nanameru/substack-mcp.git
cd substack-mcp
# Keep these values out of shell history when possible. Never commit them.
export SUBSTACK_TUNNEL_ID="tunnel_..."
export CONTROL_PLANE_API_KEY="sk-..."
./scripts/setup-chatgpt-work-tunnel.sh
tunnel-client run --profile substack-mcpThen open ChatGPT Plugins, create a developer-mode app, choose Tunnel under
Connection, and select the tunnel. The Mac and tunnel-client process must stay
running while ChatGPT calls the Substack tools.
The helper never prints or uploads the Substack session token. It runs
substack-mcp-setup locally, stores the credential with 0600 permissions, and
points the tunnel at the local stdio command.
(Optional) Install the substack-article skill
This repo also ships a Vercel Skills-compatible agent skill that uses the MCP above to plan, draft, generate a thumbnail, and publish a Substack article in one flow.
# Globally for all projects
npx skills add nanameru/substack-mcp -g -a claude-code -y
# Or scoped to the current project
npx skills add nanameru/substack-mcp -a claude-codeRestart Claude Code. The skill is automatically invoked when you say things like 「Substack 記事を書いて」 / 「Substack に投稿して」 / 「短文ポストして」.
npx skills works with Claude Code, Cursor, Codex, OpenCode, GitHub Copilot,
and 50+ other agents. See skills.sh for details.
The skill expects:
substack-mcpregistered as an MCP (above)codexMCP registered (for thumbnail generation; optional if you skip thumbnails)macOS with Hiragino fonts installed (default on macOS for Japanese text overlay)
See skills/substack-article/SKILL.md for the full flow and guardrails.
How auth works
By default substack-mcp-setup reads the substack.sid cookie directly from
your existing Chrome session via pycookiecheat.
Substack can't tell anything was automated because nothing was: it's the
same session you're already using.
macOS will prompt once for Keychain access ("Chrome Safe Storage"). Click "Always Allow" so it doesn't ask again next time.
Supports: Chrome, Brave, Edge, Chromium, Vivaldi, Opera.
Fallback modes
# Specific browser
substack-mcp-setup --from-browser brave
# Playwright-based (often blocked by Substack — use --chrome instead)
substack-mcp-setup --browser
# Manual paste from DevTools
substack-mcp-setup --manualTokens are stored at ~/Library/Application Support/substack-mcp/config.json
with 0600 permissions.
Security
The substack.sid cookie is equivalent to a password — anyone with it has
full account access (publish posts, edit billing, etc.). Treat it as such.
Where the token lives
macOS:
~/Library/Application Support/substack-mcp/config.json(mode0600)Linux:
~/.config/substack-mcp/config.json(mode0600)Or via env vars:
SUBSTACK_PUBLICATION_URL+SUBSTACK_SESSION_TOKEN(env vars are inherited by child processes — be aware when spawning subprocesses)
The .gitignore excludes config.json; never commit it. The MCP also writes
a temporary cookie file via tempfile.mkstemp (mode 0600) and deletes it in
a finally block — see auth.py:write_cookie_file.
If a token leaks
Sign out of all sessions: Substack → Settings → Security → "Sign out of all sessions". This invalidates every existing
substack.sidimmediately.Log back in to Substack in your browser.
Re-run
substack-mcp-setupto capture the new cookie.
Image upload safety
upload_image only accepts:
HTTP(S) URLs, or
Local files with image extensions (
.png,.jpg,.jpeg,.gif,.webp,.heic,.heif) that are not under sensitive system paths (/etc,/System,~/.ssh,~/.aws,~/Library/Keychains, etc.)
This guards against an assistant being tricked (via prompt injection in fetched content) into uploading e.g. an SSH private key to Substack's CDN.
Known limitation: Markdown image syntax  inside create_draft
is processed by python-substack and bypasses this validation. If you pass
untrusted Markdown, sanitize image paths first.
Dependencies
Versions are pinned with ~= (compatible release, no major bumps). Bumping
python-substack in particular should be reviewed — it talks to Substack's
private API and lives outside Substack's official surface.
Notes
audienceaccepts:everyone(default),only_paid,founding,only_free.Markdown image syntax
auto-uploads local files when you callcreate_draft.The cover image (set via
set_cover_image) is what appears on your publication homepage and in social shares. If you don't set one explicitly, Substack typically uses the first image in the body.
Available Tools
11 toolscreate_draftA
Create a new Substack draft post from Markdown.
Args:
title: Post title (max 280 chars).
content_markdown: Body in Markdown. Supports headings, bold/italic, links,
bullet lists, blockquotes, code blocks, and images ( - local
paths are auto-uploaded to Substack CDN).
subtitle: Optional subtitle (max 280 chars).
audience: Who can read it: 'everyone' (default), 'only_paid', 'founding',
or 'only_free'.
Returns: Summary including post_id, title, edit_url.
Inputs: title (required), content_markdown (required), subtitle, audience.
delete_draftA
Permanently delete a draft. This cannot be undone.
Args: post_id: Draft ID.
Returns: {post_id, deleted: true}
Inputs: post_id (required).
get_draftA
Get full details of a specific draft, including the body content.
Args: post_id: Draft ID.
Returns: Full draft data including draft_body (ProseMirror JSON).
Inputs: post_id (required).
list_draftsA
List recent drafts (unpublished posts).
Args: limit: Max number of drafts to return (1-50). Default 10.
Returns: List of draft summaries with post_id, title, edit_url, etc.
Inputs: limit. Returns: result
post_noteA
Post a Note (Substack's short-form, X/Threads-like post) to the public feed.
Notes are different from Posts:
No title or subtitle.
No email delivery to subscribers.
Not added to the publication's article archive.
Visible in the Substack Notes feed (cross-publication discovery surface).
Use Notes for:
Quick thoughts, links, restacks, questions to your audience
Daily presence between long-form posts
Networking with other Substackers (replies, mutual follows)
Args: text: Plain text body. Use \n\n to separate paragraphs and \n for soft line breaks. Max 4000 chars (Substack's practical Notes limit).
Returns: {note_id, url, raw}
Inputs: text (required).
publish_draftA
Publish a draft immediately.
Args: post_id: Draft ID. send_email: If True (default), send the post as an email to subscribers. If False, publish to web only without emailing. share_automatically: If True, auto-share to Substack social channels.
Returns: {post_id, title, public_url, post_date, send_email}
Inputs: post_id (required), send_email, share_automatically.
schedule_draftA
Schedule a draft to publish at a future datetime.
Args: post_id: Draft ID. iso_datetime: ISO 8601 datetime, e.g., '2026-05-15T09:00:00+09:00' for JST or '2026-05-15T00:00:00Z' for UTC.
Returns: {post_id, scheduled_for}
Inputs: post_id (required), iso_datetime (required).
set_cover_imageA
Set the cover (thumbnail) image for a draft.
The cover image is shown on the publication homepage, in social shares, and as the email header. Use upload_image first to get a CDN URL.
Args: post_id: Draft ID. image_url: Substack CDN URL from upload_image, or any public image URL.
Returns: Updated draft summary.
Inputs: post_id (required), image_url (required).
unschedule_draftB
Cancel a scheduled publish, keeping the post as a draft.
Args: post_id: Draft ID.
Returns: {post_id}
Inputs: post_id (required).
update_draftA
Update an existing draft. Provide only the fields you want to change.
Args: post_id: Draft ID returned by create_draft or list_drafts. title: New title (optional). subtitle: New subtitle (optional). content_markdown: New body in Markdown (optional, replaces full body). audience: New audience setting (optional).
Returns: Updated draft summary.
Inputs: post_id (required), title, subtitle, content_markdown, +1 more.
upload_imageA
Upload an image to Substack's CDN and return its public URL.
Args: image_path: Local file path (e.g., /Users/foo/cover.png) or remote URL.
Returns:
{url, id, width, height} — pass url to set_cover_image or embed in
Markdown as .
Inputs: image_path (required).
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 11 tools
Each tool has a clearly distinct purpose: draft CRUD, publishing, scheduling, notes, and image operations. No two tools overlap in functionality.
All tools follow a consistent verb_noun pattern with underscores (e.g., create_draft, publish_draft, upload_image), making the API predictable.
11 tools is well-scoped for a Substack server, covering drafting, publishing, scheduling, notes, and images without being excessive.
The tool set covers core drafting and publishing workflows comprehensively, but lacks tools for managing published posts, subscribers, or analytics, which are minor gaps for a content creation MCP.
Maintenance
Related MCP Connectors
Substack MCP server for ChatGPT, Claude and any AI agent: create post, schedule post, publish Note, reply to Notes, send direct message, moderate comments, list subscribers and get post stats in your own Substack account. Cotlet works in the account through your Cloud Browser, a real Chrome where you sign in to Substack yourself; Cotlet does not store your password and you need no Substack API key. About the official Substack API: Substack has only a limited API (checked 11 Oct 2026). Its Developer API looks up public creator profiles after approval, and there is no public API to write posts or Notes, manage subscribers or send email. Setup and FAQ: https://cotlet.dev/integrations/substack-api-mcp-server
MCP server for QPost — lets AI agents publish video and image posts to YouTube, TikTok, Instagram.
PostHero MCP server — schedule and publish social media posts from any MCP-compatible AI tool
Hosted MCP server for AI agents to post to LinkedIn profiles and X and upload to YouTube.
Related MCP Servers
- AlicenseNot gradedqualityNot gradedmaintenanceAn MCP server that enables AI assistants like Claude to interact with Substack newsletters, allowing for post retrieval, content searching, and author information access through a standardized interface.MIT
- AlicenseAqualityAmaintenanceMCP server and CLI for Substack creators: rich Markdown drafts, draft tags, Notes, publication and post analytics, growth sources, archive search, anonymous reading of public profiles and Note threads, subscriber segmentation and consented subscriber management. Long-form posts stay drafts; Notes publish immediately.341,542 npm46MIT
- 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
- AlicenseNot gradedqualityDmaintenanceCreate, manage, and publish Substack posts with full rich text formatting directly from Claude Desktop or any MCP-compatible client.71 npm26MIT