WriteStack MCP server
Connect Claude, ChatGPT, Notion, or any MCP client to a writer’s WriteStack account. Draft and schedule Substack Notes, manage articles, and read statistics.
https://www.writestack.io/api/mcp/mcp- Transport
- Streamable HTTP
- Auth
- OAuth 2.1 or API key
- Tools
- 28
Quick start
- 1
Add the endpoint
In Claude, ChatGPT, or Notion, add a custom MCP connector and paste the endpoint URL.
- 2
Sign in to WriteStack
The client opens WriteStack. Sign in and approve access. No API key or config file needed.
- 3
Ask in plain language
Try “Draft three Notes about my last post and queue them.” The client picks the tools.
One remote MCP server, scoped to the writer who signed in.
A connected client can draft and update Substack Notes, place them on the writer’s queue, work with articles, read statistics and activity, and set Buffer cross-posts. Every call runs as the authenticated WriteStack user and never touches another account.
- Endpoint
- https://www.writestack.io/api/mcp/mcp
- Transport
- Streamable HTTP
- Legacy SSE probe
- https://www.writestack.io/api/mcp/mcp/sse returns HTTP 405. Use the endpoint above.
- Acts as
- The signed-in WriteStack user
Every request needs a bearer token. Use OAuth when the client supports it, and an API key when it does not.
- Flow
- OAuth 2.1, authorization code with PKCE (S256)
- Authorize
- https://www.writestack.io/oauth/authorize
- Token
- https://www.writestack.io/api/oauth/token
- Client registration
- https://www.writestack.io/api/oauth/register
- Server metadata
- https://www.writestack.io/.well-known/oauth-authorization-server
- Resource metadata
- https://www.writestack.io/.well-known/oauth-protected-resource
- Scopes
- mcp, offline_access
- Grant types
- authorization_code, refresh_token
- Client auth
- Public clients, no secret (token_endpoint_auth_method: none)
For clients without OAuth, create an API key in WriteStack under Settings → MCP (www.writestack.io/settings) and send it as Authorization: Bearer <key>. Treat the key like a password. Revoke it in Settings if it leaks.
Plan access
MCP is included on eligible WriteStack plans. A call from an account without access returns an error that points the writer to Settings.
Claude, ChatGPT, and Notion connect with OAuth. Paste the endpoint, then sign in.
A client that reads a local config file and has no OAuth support can reach the server through mcp-remote with an API key header:
{
"mcpServers": {
"writestack": {
"command": "npx",
"args": [
"-y",
"mcp-remote",
"https://www.writestack.io/api/mcp/mcp",
"--header",
"Authorization: Bearer ws_your_api_key"
]
}
}
}Draft, read, and update Substack Notes in the writer’s voice.
Note bodies are Markdown, and a line break is a blank line (two newlines). Before drafting new text, call get_writing_style unless the writer already pasted full style instructions. Call list_tags before assigning tag names.
Remote images go in imageUrl, up to five https URLs per note. WriteStack fetches and stores them.
Ask before adding link previews
If a body contains a link, ask the writer before attaching a preview card. Pass includePreviews: true only after they say yes.
- list_
notesRead List notes, optionally by status or tag. Status
evergreenmeans notes set to repeat. Paginate withskipand readhasMore. - get_
noteRead Read one note, including auto-restack, Buffer channels, and any per-channel Buffer text or images.
Read the saved note style, topics, and onboarding context.
- create_
noteWrite Create a draft or ready note. Optional auto-restack 1–168 hours after it publishes, plus optional Buffer channels and per-channel posts.
- create_
many_ notesWrite Create several notes in one call. Each note has its own status, tags, images, previews, auto-restack, and Buffer settings.
- update_
noteIrreversible Replace the fields you pass: content, status, tags, auto-restack, Buffer channels, per-channel posts, or link preview. No undo, so read the note and confirm first.
autoRestackAfterHours: nullturns auto-restack off, and an emptybufferChannelIdsclears channels.
Queue Notes on the writer’s weekly template or at exact times.
The template is a set of weekly times. Each time stays open, reserves tags, or accepts only untagged notes. A note takes a reserved time when it shares any of that time’s tags. Untagged notes use open and untagged-only times. A time can also carry default Buffer channels, which replace the note’s channels when it lands there.
The writer syncs to Substack
Scheduling stores the queue in WriteStack. It does not publish to Substack. After a schedule call, the writer opens WriteStack and clicks the yellow Sync button so the Chrome extension syncs the queue. Ask them to confirm they will, then pass userConfirmedManualSync: true. Never set it without an explicit yes.
Check capacity first
Call get_schedule_capacity before scheduling. Schedule at most remaining notes unless unlimited is true. The rest stay as drafts or ready notes.
- get_
scheduleRead Read the weekly template, including reserved tags, untagged-only times, and default Buffer channels.
- get_
queueRead List notes that already have a posting time. Paginate with
skip. Preview the next free times. Pass
tagNamesto preview one note’s lane, or omit them to list every free time in order.Read the plan limit, the current scheduled count, how many remain, and whether the account is unlimited.
- schedule_
notesIrreversible Place notes on the next template times, routed by each note’s tags. Use this unless the writer asks for exact times.
- schedule_
notes_ with_ custom_ datesIrreversible Place each note at an explicit future ISO 8601 time. Use this when the writer names times or has no template.
Plan, write, and push long-form articles to Substack drafts.
Article tools only see newsletters the writer enabled in WriteStack Articles. Call list_article_newsletters and ask which one to use before creating, pushing, or scheduling. Never guess a publicationId.
Bodies are GitHub-flavored Markdown that Substack accepts: headings, bold, italic, strikethrough, inline code, links, images, lists, blockquotes, :::pullquote, :::callout, and a :::paywall::: line. Voice and video embeds are not supported.
create_article and update_article save in WriteStack only. Pushing and scheduling go through the writer’s Chrome extension. With WriteStack open, a push usually finishes in seconds. Otherwise it waits until WriteStack is next open.
Read before you write
Call get_article right before update_article and pass its contentVersion as baseVersion. A version conflict means the writer edited it, so read it again. Pushing an article that already has a Substack draft overwrites that draft.
- list_
articlesRead List articles from enabled newsletters, optionally by status or
publicationId. - get_
articleRead Read one article: Markdown body, newsletter, content version, and Substack draft or schedule status.
- create_
articleWrite Create a planned article on the newsletter the writer chose.
- update_
articleIrreversible Replace title, subtitle, body, images, or Substack settings (audience, comments, SEO, slug, social title,
publishAt) in WriteStack. No undo. - generate_
articleWrite Start AI generation for a planned article. Returns right away while the draft is written in the background.
- push_
article_ to_ substackIrreversible Send or update the article as a Substack draft through the Chrome extension.
Schedule the article on Substack at
publishAt. Pushes the draft first if it is not synced.- unschedule_
article_ on_ substackIrreversible Remove the article from the Substack schedule. The draft stays on Substack.
Read the draft URL, scheduled time, and last sync error.
Read what worked, and what readers are saying.
Statistics tools need the matching WriteStack feature and a current extension. If a call fails, the error text says what to do next.
Published Note analytics: impressions, clicks, reactions, comments, restacks, profile visits, shares, subscribers, and an audience split when Substack stored one. Filter by date, sort, search, and paginate.
Post statistics for every newsletter on Posts Statistics: views, opens, open rate, delivery, likes, comments, restacks, shares, clicks, signups, subscriber split, and ARR. Use the returned
openRate; email traffic share is a different number. Needs extension 1.5.25 or newer.- list_
activitiesRead Substack comments and mentions, the same as the WriteStack activity feed, without the writer’s own posts. Paginate with
skipandlimit(at most 50).
Send a Note to LinkedIn, X, Threads, and more after it goes live on Substack.
Call list_buffer_channels before passing channel ids. Only returned ids are accepted, up to 20 per note. An empty list means the writer has not connected Buffer.
Instagram needs media
Instagram feed images must be between 4:5 and 1.91:1, or Buffer falls back to Notify Me. See support.buffer.com/article/622-instagrams-accepted-aspect-ratio-ranges
List connected Buffer channels with id, name, and service.
When the writer asks for something the server cannot do.
Return writestack.io/features, where the writer can post the idea for votes. It does not submit anything.
What the server will not do, and what clients must not assume.
- The server cannot publish Notes to Substack. The writer syncs with the yellow Sync button in WriteStack.
- The server cannot unschedule Notes. The writer does that in WriteStack.
update_noteandupdate_articleoverwrite the fields you send. Read first, confirm, then write.- Never schedule more Notes than
get_schedule_capacityreports, unlessunlimitedis true. - Never invent publication ids, tag names, or Buffer channel ids. Read them from the matching list tool.
- Auto-restack runs 1–168 hours after a Note publishes and needs WriteStack extension 1.4.78 or newer.
- Personal data practices are in the privacy policy at www.writestack.io/privacy. Questions go to orel@writestack.io.