WriteStack
Connect Claude, ChatGPT, Notion, or any MCP client to a writer’s WriteStack account. Draft and schedule Substack Notes, manage articles, and read statistics.
Hosted MCP Server
npx add-mcp 'https://www.writestack.io/api/mcp/mcp'Installs into Claude Code, Codex, Cursor and more
Documentation
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.
Endpoint: https://www.writestack.io/api/mcp/mcp
Quick start
- Add the endpoint. In Claude, ChatGPT, or Notion, add a custom MCP connector and paste the endpoint URL.
- Sign in to WriteStack. The client opens WriteStack. Sign in and approve access. No API key or config file needed.
- Ask in plain language. Try “Draft three Notes about my last post and queue them.” The client picks the tools.
Server
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
Authentication
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 (https://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.
Connect a client
Claude, ChatGPT, and Notion connect with OAuth. Paste the endpoint, then sign in.
- Claude: Claude Desktop and Claude on the web
- ChatGPT: ChatGPT Desktop and the web app
- Notion: Notion AI custom connectors
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"
]
}
}
}
Notes
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: trueonly after they say yes.
list_notes(Read): List notes, optionally by status or tag. Statusevergreenmeans notes set to repeat. Paginate withskipand readhasMore.get_note(Read): Read one note, including auto-restack, Buffer channels, and any per-channel Buffer text or images.get_writing_style(Read): Read the saved note style, topics, and onboarding context.list_tags(Read): List category names. Tags also decide which reserved queue times a note can take.create_note(Write): 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_notes(Write): Create several notes in one call. Each note has its own status, tags, images, previews, auto-restack, and Buffer settings.update_note(Irreversible): 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.
Schedule
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_capacitybefore scheduling. Schedule at mostremainingnotes unlessunlimitedis true. The rest stay as drafts or ready notes.
get_schedule(Read): Read the weekly template, including reserved tags, untagged-only times, and default Buffer channels.get_queue(Read): List notes that already have a posting time. Paginate withskip.get_next_available_slots(Read): Preview the next free times. PasstagNamesto preview one note’s lane, or omit them to list every free time in order.get_schedule_capacity(Read): Read the plan limit, the current scheduled count, how many remain, and whether the account is unlimited.schedule_notes(Irreversible): 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_dates(Irreversible): Place each note at an explicit future ISO 8601 time. Use this when the writer names times or has no template.
Articles
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_articleright beforeupdate_articleand pass itscontentVersionasbaseVersion. 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_article_newsletters(Read): List enabled newsletters withpublicationId, title, URL, and which one is primary.list_articles(Read): List articles from enabled newsletters, optionally by status orpublicationId.get_article(Read): Read one article: Markdown body, newsletter, content version, and Substack draft or schedule status.create_article(Write): Create an article draft on the newsletter the writer chose, with an optional target date.update_article(Irreversible): Replace title, subtitle, body, images, or Substack settings (audience, comments, SEO, slug, social title,publishAt) in WriteStack. No undo.push_article_to_substack(Irreversible): Send or update the article as a Substack draft through the Chrome extension.schedule_article_on_substack(Write): Schedule the article on Substack atpublishAt. Pushes the draft first if it is not synced.unschedule_article_on_substack(Irreversible): Remove the article from the Substack schedule. The draft stays on Substack.get_article_substack_status(Read): Read the draft URL, scheduled time, and last sync error.
Statistics and activity
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.
get_notes_statistics(Read): 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.get_posts_statistics(Read): 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 returnedopenRate; email traffic share is a different number. Needs extension 1.5.25 or newer.list_activities(Read): Substack comments and mentions, the same as the WriteStack activity feed, without the writer’s own posts. Paginate withskipandlimit(at most 50).
Buffer cross-posting
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 https://support.buffer.com/article/622-instagrams-accepted-aspect-ratio-ranges
list_buffer_channels(Read): List connected Buffer channels with id, name, and service.
Feature ideas
When the writer asks for something the server cannot do.
get_feature_suggestion_link(Read): Return https://writestack.io/features, where the writer can post the idea for votes. It does not submit anything.
Limits and rules
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 https://www.writestack.io/privacy. Questions go to orel@writestack.io.