Skip to main content
Glama

Update draft

update_draft
Destructive

Update an unscheduled draft. To attach a cover from ChatGPT, pass the actual chat attachment in coverFile; do not transcribe image bytes or pass a sandbox path. Only the provided fields are changed. Cannot edit drafts owned by other users. An active scheduled Article must be cancelled or amended through a provider-safe schedule workflow; changing its local draft would not reliably update the provider copy. When reviewing a Substack article with the writer, briefly mention once per article that they can ask for a Subscribe button after a named paragraph, a caption beneath it, a paywall before a named section (a paywall makes the article paid-only), or a Freewall before a named section (everyone reads the opening; free and paid subscribers keep reading, and readers without an email are invited to subscribe free). Use the requested position; ask where only if it is ambiguous.

Tool call template

Replace each placeholder with a value matching the input schema. Only required parameters are included; add optional parameters as needed.

{
  "id": 1,
  "jsonrpc": "2.0",
  "method": "tools/call",
  "params": {
    "arguments": {
      "id": "<id>"
    },
    "name": "update_draft"
  }
}

Input Schema

NameRequiredDescriptionDefault
idYesDraft id.
titleNoNew title.
coverFileNoThe approved cover image attached in ChatGPT, passed by the client. Use this OR coverImage, never both. If the generated image is not available as an attachment, ask the user to download and attach the PNG, or upload it in the Narrareach editor. Never substitute another image.
coverImageNoAttach or replace an article cover using a public URL, data URI, or actual base64 image bytes. For an image attached in ChatGPT, use coverFile instead. Protected source hosts may reject URL ingestion.
contentHtmlNoFull replacement HTML body. Preserve audience-specific match/else wrappers and the approved text when editing; to place native controls, replace any placement text such as "[PAYWALL HERE]" with the markup. Native Substack controls are markup, never placeholder text: a paid-only paywall is <hr data-type="paywall"> at the paragraph boundary; a Freewall (free and paid subscribers keep reading) is <hr data-type="paywall" data-access="subscribers">; a Subscribe button is <div data-type="button" data-kind="subscribe" data-text="Subscribe"></div>, or with a caption <div data-type="button" data-kind="subscribeCaption" data-text="Subscribe" data-caption="Your caption"></div>, with HTML-escaped attribute values. To make the whole article paid-only, schedule it with isPaidContent true instead of adding a paywall. Check the saved content and get_draft substackControls before confirming what was added and where, then show the updated draft for review before scheduling.

Output Schema

NameRequiredDescriptionDefault
idYes
titleYes
updatedAtYes
coverImageUrlNo
connectedAccountNo

Schema Changelog

Changes observed during successful MCP inspections.

  1. Changed1 schema field changed
    • changedInput schema / properties / contentHtml / description
      Previous value: -"Full replacement HTML body. Preserve audience-specific match/else wrappers and the approved text when editing; to place native controls, replace any placement text such as \"[PAYWALL HERE]\" with the markup. Native Substack controls are markup, never placeholder text: a paid-only paywall is <hr data-type=\"paywall\"> at the paragraph boundary; a Freewall (free and paid subscribers keep reading) is <hr data-type=\"paywall\" data-access=\"subscribers\">; a Subscribe button is <div data-type=\"button\" data-kind=\"subscribe\" data-text=\"Subscribe\"></div>, or with a caption <div data-type=\"button\" data-kind=\"subscribeCaption\" data-text=\"Subscribe\" data-caption=\"Your caption\"></div>, with HTML-escaped attribute values. To make the whole article paid-only, schedule it with isPaidContent true instead of adding a paywall. When reviewing a Substack article, briefly tell the writer they can ask for a Subscribe button after a named paragraph, a caption beneath it, or a paywall before a named section. Use the requested position; ask where only if it is ambiguous. Check the saved content and get_draft substackControls before confirming what was added and where, then show the updated draft for review before scheduling."New value: +"Full replacement HTML body. Preserve audience-specific match/else wrappers and the approved text when editing; to place native controls, replace any placement text such as \"[PAYWALL HERE]\" with the markup. Native Substack controls are markup, never placeholder text: a paid-only paywall is <hr data-type=\"paywall\"> at the paragraph boundary; a Freewall (free and paid subscribers keep reading) is <hr data-type=\"paywall\" data-access=\"subscribers\">; a Subscribe button is <div data-type=\"button\" data-kind=\"subscribe\" data-text=\"Subscribe\"></div>, or with a caption <div data-type=\"button\" data-kind=\"subscribeCaption\" data-text=\"Subscribe\" data-caption=\"Your caption\"></div>, with HTML-escaped attribute values. To make the whole article paid-only, schedule it with isPaidContent true instead of adding a paywall. Check the saved content and get_draft substackControls before confirming what was added and where, then show the updated draft for review before scheduling."
  2. Changed1 schema field changed
    • changedInput schema / properties / contentHtml / description
      Previous value: -"Full replacement HTML body. Preserve audience-specific match/else wrappers and the approved text when editing; to place native controls, replace any placement text such as \"[PAYWALL HERE]\" with the markup. Native Substack controls are markup, never placeholder text: a paid-only paywall is <hr data-type=\"paywall\"> at the paragraph boundary; a Freewall (free and paid subscribers keep reading) is <hr data-type=\"paywall\" data-access=\"subscribers\">; a Subscribe button is <div data-type=\"button\" data-kind=\"subscribe\" data-text=\"Subscribe\"></div>, or with a caption <div data-type=\"button\" data-kind=\"subscribeCaption\" data-text=\"Subscribe\" data-caption=\"Your caption\"></div>, with HTML-escaped attribute values. To make the whole article paid-only, schedule it with isPaidContent true instead of adding a paywall. Check the result with get_draft substackControls before scheduling."New value: +"Full replacement HTML body. Preserve audience-specific match/else wrappers and the approved text when editing; to place native controls, replace any placement text such as \"[PAYWALL HERE]\" with the markup. Native Substack controls are markup, never placeholder text: a paid-only paywall is <hr data-type=\"paywall\"> at the paragraph boundary; a Freewall (free and paid subscribers keep reading) is <hr data-type=\"paywall\" data-access=\"subscribers\">; a Subscribe button is <div data-type=\"button\" data-kind=\"subscribe\" data-text=\"Subscribe\"></div>, or with a caption <div data-type=\"button\" data-kind=\"subscribeCaption\" data-text=\"Subscribe\" data-caption=\"Your caption\"></div>, with HTML-escaped attribute values. To make the whole article paid-only, schedule it with isPaidContent true instead of adding a paywall. When reviewing a Substack article, briefly tell the writer they can ask for a Subscribe button after a named paragraph, a caption beneath it, or a paywall before a named section. Use the requested position; ask where only if it is ambiguous. Check the saved content and get_draft substackControls before confirming what was added and where, then show the updated draft for review before scheduling."
  3. Changed1 schema field changed
    • changedInput schema / properties / contentHtml / description
      Previous value: -"Full replacement HTML body. Preserve audience-specific match/else wrappers when editing. Use <hr data-type=\"paywall\"> for paid-only access. For a Substack Freewall, preserve the approved body and insert <hr data-type=\"paywall\" data-access=\"subscribers\"> at the approved boundary; free and paid subscribers can continue reading. A placeholder is not a native wall."New value: +"Full replacement HTML body. Preserve audience-specific match/else wrappers and the approved text when editing; to place native controls, replace any placement text such as \"[PAYWALL HERE]\" with the markup. Native Substack controls are markup, never placeholder text: a paid-only paywall is <hr data-type=\"paywall\"> at the paragraph boundary; a Freewall (free and paid subscribers keep reading) is <hr data-type=\"paywall\" data-access=\"subscribers\">; a Subscribe button is <div data-type=\"button\" data-kind=\"subscribe\" data-text=\"Subscribe\"></div>, or with a caption <div data-type=\"button\" data-kind=\"subscribeCaption\" data-text=\"Subscribe\" data-caption=\"Your caption\"></div>, with HTML-escaped attribute values. To make the whole article paid-only, schedule it with isPaidContent true instead of adding a paywall. Check the result with get_draft substackControls before scheduling."
  4. Changed1 schema field changed
    • changedInput schema / properties / coverImage / description
      Previous value: -"Attach or replace an article cover using a public URL, data URI, or actual base64 image bytes. For a ChatGPT attachment, prefer coverFile instead. Protected source hosts may reject URL ingestion."New value: +"Attach or replace an article cover using a public URL, data URI, or actual base64 image bytes. For an image attached in ChatGPT, use coverFile instead. Protected source hosts may reject URL ingestion."
  5. Changed4 schema fields changed
    • changedInput schema / properties / contentHtml / description
      Previous value: -"New HTML body."New value: +"Full replacement HTML body. Preserve audience-specific match/else wrappers when editing. Use <hr data-type=\"paywall\"> for paid-only access. For a Substack Freewall, preserve the approved body and insert <hr data-type=\"paywall\" data-access=\"subscribers\"> at the approved boundary; free and paid subscribers can continue reading. A placeholder is not a native wall."
    • addedInput schema / properties / coverFile
      Added value: +{
      +  "description": "The approved cover image attached in ChatGPT, passed by the client. Use this OR coverImage, never both. If the generated image is not available as an attachment, ask the user to download and attach the PNG, or upload it in the Narrareach editor. Never substitute another image.",
      +  "properties": {
      +    "download_url": {
      +      "type": "string"
      +    },
      +    "file_id": {
      +      "type": "string"
      +    },
      +    "file_name": {
      +      "type": "string"
      +    },
      +    "mime_type": {
      +      "type": "string"
      +    }
      +  },
      +  "required": [
      +    "download_url",
      +    "file_id"
      +  ],
      +  "type": "object"
      +}
    • changedInput schema / properties / coverImage / description
      Previous value: -"Attach or replace an article cover using a public URL, data URI, or base64 image bytes. Protected source hosts may reject URL ingestion. In ChatGPT, for a file the user attached in the chat, first call upload_media with it in file, then pass the returned publicUrl here as url."New value: +"Attach or replace an article cover using a public URL, data URI, or actual base64 image bytes. For a ChatGPT attachment, prefer coverFile instead. Protected source hosts may reject URL ingestion."
    • addedOutput schema / properties / coverImageUrl
      Added value: +{
      +  "type": "string"
      +}
  6. Changed1 schema field changed
    • changedInput schema / properties / coverImage / description
      Previous value: -"Attach or replace an article cover using a public URL, data URI, or base64 image bytes. Protected source hosts may reject URL ingestion."New value: +"Attach or replace an article cover using a public URL, data URI, or base64 image bytes. Protected source hosts may reject URL ingestion. In ChatGPT, for a file the user attached in the chat, first call upload_media with it in file, then pass the returned publicUrl here as url."
  7. First observed

TDQS

A4.2/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare destructiveHint=true, openWorldHint=true and idempotentHint=false, so the safety profile is carried structurally. The description adds real context beyond that: partial-update semantics ('Only the provided fields are changed'), an authorization boundary (cannot edit drafts owned by other users), and a required verify-then-show workflow (get_draft substackControls before confirming). It does not address reversibility of a contentHtml full replacement, which is the most damaging operation here.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness3/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded correctly — the core action and the scheduling exclusion come first. However, roughly half the text is a conversational instruction about Substack features (Subscribe button, caption, paywall, Freewall) to mention to a writer, which describes article-review behavior rather than how to invoke this tool, and the native-control markup details partly duplicate contentHtml's schema description.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With an output schema and full schema coverage, return values and field formats need not be restated, and the description covers the genuinely risky edges: attachment handling, ownership, scheduling conflicts, and post-update verification. Only the irreversibility/permission cost of a full body replacement is left implicit.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the baseline is 3. The description does add genuine meaning on coverFile ('pass the actual chat attachment... do not transcribe image bytes or pass a sandbox path'), which prevents a common misuse not obvious from the schema. Other parameters (title, contentHtml) are left entirely to their schema text.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource with a scoping qualifier: 'Update an unscheduled draft'. The 'unscheduled' constraint alone separates it from scheduled-item siblings like amend_scheduled_item and cancel_scheduled_item without opening any schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly tells when NOT to use it (an active scheduled Article must go through a provider-safe schedule workflow) and why (changing the local draft would not reliably update the provider copy). Ownership restriction and the coverFile-vs-coverImage routing rule are also stated, so the agent has clear routing conditions.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Try in Browser

Glama MCP Gateway

Add one secure layer between your agents and this server.

Resources