update_article
Update Article. Update an article's title, subtitle, markdown body, imageUrl (remote images fetched to S3 and appended), or Substack settings (audience, comments, SEO, slug, social title, publishAt).
| Group | Access | MCP name |
|---|---|---|
| Articles | Irreversible | update_article |
Description
The description the MCP server sends to the model, word for word. Its rules apply to every call.
Update an article's title, subtitle, markdown body, imageUrl (remote images fetched to S3 and appended), or Substack settings (audience, comments, SEO, slug, social title, publishAt). This saves locally only — it does not push to Substack. Pass baseVersion from get_article; a version conflict means you must re-read the article. Images in bodyMarkdown are fetched to S3. Body markdown supports the same Substack-safe formats as create_article (headings, bold/italic/strike/code, lists, quotes, pullquotes, callouts, paywall, links, images). BEFORE calling this tool, you MUST call get_article for the same articleId and base the update on that fresh copy — the user may have edited the article in WriteStack. Never update from a stale draft you wrote earlier in the conversation. Replaces the article's current content and settings with the values you pass; there is no undo through this integration.
Arguments
| Argument | Type | Required | Description |
|---|---|---|---|
articleId | string | Yes | |
audience | string | No | |
baseVersion | integer | No | contentVersion from get_article. Pass it to avoid overwriting newer edits. |
bodyMarkdown | string | No | GitHub-flavored Markdown body. Supported and synced to Substack: headings #–######, bold, italic, inline code, links, images , bullet and numbered lists, > blockquotes, :::pullquote ... :::, :::callout ... :::, and a paid-only split with a standalone :::paywall::: line (content after it is behind the Substack paywall). Voice and video embeds are not supported. |
commentPermissions | string | No | |
imageUrl | string[] | No | Optional image URLs to insert into the article. WriteStack fetches each URL, stores the image on S3, and appends it to the markdown body. For an image that has no public URL (a local file, a private Google Drive file), call create_article_image_upload first, upload the file, and pass the imageUrl it returned. Markdown/HTML images already in bodyMarkdown are ingested the same way. Max 20. Pass full https:// URLs. Invalid or unreachable URLs are skipped. Images are uploaded to Substack when the article is opened or saved with the Chrome extension, and again when pushing the draft. Max items 20. |
publishAt | string | null | No | |
seoDescription | string | null | No | |
seoTitle | string | null | No | |
slug | string | null | No | |
socialTitle | string | null | No | |
subtitle | string | null | No | |
title | string | No |
Example call
The smallest valid request: required arguments only, with placeholder values.
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "update_article",
"arguments": {
"articleId": "…"
}
}
}Result
The result has structuredContent that matches this schema. The same data is also in content as JSON text.
| Field | Type | Required | Description |
|---|---|---|---|
articleId | string | Yes | |
contentVersion | number | No | |
images | object[] | No | |
images[].s3Url | string | Yes | |
images[].sourceUrl | string | No | |
message | string | Yes | |
status | string | Yes |
Annotations
| Hint | Value |
|---|---|
readOnlyHint | false |
destructiveHint | true |
openWorldHint | true |
The full input and output JSON Schemas for every tool are in /mcp-tools.json.