MCPInstall

publish-substack-article

Publish Markdown articles to Substack as drafts. Use when user wants to publish a Markdown file to Substack, or mentions "发布到 Substack", "Substack article", "publish to Substack". Handles Markdown-to-HTML conversion and saves as draft (never auto-publish).

Installs
605
Last 24h
—
Trending rank
—
Security
Not audited

Install

Works with every agent the skills CLI supports.

npx skills add https://github.com/sugarforever/01coder-agent-skills --skill publish-substack-article
  1. 01

    Install

    Run the command in your project. The CLI asks which agents to install for.

  2. 02

    Describe the task

    Ask your agent for something this skill covers. It loads publish-substack-article automatically when relevant.

  3. 03

    Review the result

    The agent follows the SKILL.md steps, scripts and references bundled with the skill.

SKILL.md

The instructions your agent reads when the skill is triggered.

Publish Substack Article

Publish Markdown content to Substack post editor, converting Markdown to HTML and pasting as rich text. Saves as draft for user review before publishing.

Prerequisites

  • Browser automation MCP (either one):
    • Chrome DevTools MCP (mcp__chrome-devtools__*)
    • Playwright MCP (mcp__playwright__*)
  • User logged into Substack
  • Python 3 with markdown package (pip install markdown)
  • copy_to_clipboard.py script (shared from publish-zsxq-article skill)

Browser MCP Tool Mapping

This skill works with both Chrome DevTools MCP and Playwright MCP:

ActionChrome DevTools MCPPlaywright MCP
Navigatenavigate_pagebrowser_navigate
Take snapshottake_snapshotbrowser_snapshot
Take screenshottake_screenshotbrowser_take_screenshot
Click elementclickbrowser_click
Fill textfillbrowser_type
Upload fileupload_filebrowser_file_upload
Press keypress_keybrowser_press_key
Evaluate JSevaluate_scriptbrowser_evaluate

Priority: Default to Playwright MCP. Use Chrome DevTools MCP only when Playwright MCP is unavailable.

Detection: At runtime, prefer mcp__playwright__browser_navigate. Fall back to mcp__chrome-devtools__navigate_page only if Playwright tools are not available.

Key URLs

  • Substack dashboard: https://{publication}.substack.com/publish
  • Post editor: https://{publication}.substack.com/publish/post/{postId}

Publication Resolution

The Substack publication subdomain (e.g., verysmallwoods in verysmallwoods.substack.com) is required to navigate to the dashboard. Do not hardcode a default.

Before any navigation, resolve the publication in this order:

  1. Skill argument — if the user invoked the skill with a publication subdomain, use it.
  2. Environment variable — check SUBSTACK_PUBLICATION (optional, for users who publish repeatedly to the same publication).
  3. Prompt the user if neither is available:

    请提供 Substack publication subdomain(例如 verysmallwoods.substack.com 里的 verysmallwoods)。

Do not proceed to Step 3 (Navigate) without a resolved publication subdomain.

Editor Interface

The Substack post editor uses Tiptap (ProseMirror-based WYSIWYG editor).

Key Elements

  • Title input: textbox "title" (placeholder: "Title")
  • Subtitle input: textbox "Add a subtitle…"
  • Content area: .ProseMirror (Tiptap editor, "Start writing...")
  • Save status: button "Saved" (auto-saves)
  • Preview button: button "Preview"
  • Continue button: button "Continue" (publish flow - DO NOT USE)
  • Settings sidebar: button "Settings" (title, description, thumbnail)

Settings Sidebar (left panel)

When "Settings" or "File Settings" is open:

  • Title: textbox "Add a title..."
  • Description: textbox "Add a description..."
  • Thumbnail: Upload button (3:2 aspect ratio)

Toolbar

Bold, Italic, Strikethrough, Code, Link, Image, Audio, Video, Quote, Lists (bullet/ordered), Button, More (Code block, Divider, Footnote, LaTeX, etc.)

Content Insertion Method

CRITICAL: Use clipboard paste with HTML content, NOT direct fill or plain Markdown paste.

The Tiptap editor handles HTML paste natively and renders it as rich content. The workflow is:

  1. Convert Markdown to HTML using Python's markdown library
  2. Copy HTML to system clipboard using copy_to_clipboard.py html
  3. Focus the editor content area
  4. Press Cmd+V (macOS) or Ctrl+V (Windows/Linux) to paste

Why HTML paste?

  • fill tool → Content treated as plain text, no formatting
  • Plain Markdown paste → Tiptap does NOT parse Markdown on paste
  • HTML paste → Tiptap renders HTML as rich content (headings, code blocks, links, bold, etc.)

Known limitation: Substack's editor does NOT support HTML tables. Tables will be collapsed into plain text. See Step 0: Pre-Processing for converting tables to images.

Main Workflow

Step 0: Pre-Processing — Convert Tables to Images

Substack does NOT render HTML tables. They collapse into plain text. Any Markdown table must be converted to a PNG image and uploaded separately.

Workflow:

  1. Detect tables in the Markdown file (lines with | forming table structure)

  2. Convert each table to PNG using the diagram-to-image skill:

# Extract table to temp file
cat > /tmp/table1.md << 'TABLE_EOF'
| Column 1 | Column 2 | Column 3 |
|----------|----------|----------|
| Data 1   | Data 2   | Data 3   |
TABLE_EOF

# Convert via diagramless.xyz API (auto-detects as table)
node ~/.claude/skills/diagram-to-image/scripts/diagram-to-image.mjs /tmp/table1.md -o /tmp/table1.png
  1. Note the position of each table in the article for later insertion (after which heading/paragraph)

  2. Remove table Markdown from the content before HTML conversion (so it won't appear as plain text in the pasted content)

Image upload happens after pasting the main content — see Step 7.

Step 1: Prepare Content

Read the Markdown file and extract:

  • Title: from YAML frontmatter title field, or H1 header # Title, or filename
  • Subtitle: from YAML frontmatter excerpt or description field
  • Content: full Markdown body (strip YAML frontmatter and any cross-reference links)

Step 2: Convert Markdown to HTML

Use Python's markdown library with tables and fenced_code extensions:

import markdown
import re

with open('/path/to/article.md', 'r') as f:
    content = f.read()

# Strip YAML frontmatter
content = re.sub(r'^---\n.*?\n---\n', '', content, flags=re.DOTALL)

# Strip cross-reference links (e.g., English version link)
# Adjust pattern as needed for your articles
content = re.sub(r'^> .* available at.*\n\n?', '', content, flags=re.MULTILINE)

# Convert to HTML
html = markdown.markdown(content, extensions=['tables', 'fenced_code'])

# Write to temp file
with open('/tmp/substack_article.html', 'w') as f:
    f.write(html)

IMPORTANT: Do NOT use nl2br extension - it converts single newlines to <br> tags, causing extra line breaks in the editor.

Step 3: Navigate to Substack

Navigate to the Substack dashboard and create a new post:

# Navigate to Substack dashboard
navigate to: https://{publication}.substack.com/publish

If not logged in, prompt user to log in:

请先登录 Substack,登录完成后告诉我。
Please log in to Substack first, then let me know.

Step 4: Create New Post

From the dashboard, create a new text post:

  1. Click "Create new" in the sidebar
  2. Select "Text post" (or navigate directly to a new post URL)

Alternatively, if the editor is already open with an empty post, proceed directly.

Step 5: Fill Title and Subtitle

  1. Click the title textbox (textbox "title")
  2. Type the article title
  3. Click the subtitle textbox (textbox "Add a subtitle…")
  4. Type the subtitle/excerpt
click: title textbox
fill/type: article title

click: subtitle textbox
fill/type: article subtitle

Step 6: Insert HTML Content (via Clipboard Paste)

CRITICAL: Do NOT use fill tool - it inserts plain text without formatting.

  1. Copy HTML to system clipboard:
python3 /path/to/copy_to_clipboard.py html --file /tmp/substack_article.html
  1. Click the editor content area (.ProseMirror or paragraph element inside it)

  2. Press Cmd+V to paste:

press_key: Meta+v  (macOS)
press_key: Control+v  (Windows/Linux)

This triggers Tiptap's HTML paste handler, which renders the content as rich text with proper formatting.

Step 7: Insert Table Images

If the article had tables converted to images in Step 0, insert them now:

  1. Navigate to the correct position in the editor — click on the paragraph or empty line where the table should appear (after the relevant heading/text)

  2. Click the Image toolbar button (button "Image") — a dropdown menu appears with options: Image, Gallery, Stock photos, Generate image

  3. Click "Image" menuitem from the dropdown — a file chooser dialog opens

  4. Upload the image via file chooser:

    • Playwright MCP: browser_file_upload with the image path
    • Chrome DevTools MCP: upload_file with the image path

Important notes:

  • File path restriction: Playwright MCP only allows file uploads from within allowed roots (project directories). If your image is in /tmp/, copy it to the project directory first
  • Repeat for each table: Position cursor at the correct location, then upload each table image
  • Delete residual text: If table content was pasted as plain text (because it wasn't removed in pre-processing), select it (triple-click to select paragraph) and delete before inserting the image

Step 8: Verify Draft

After pasting:

  1. Check the "Saved" status indicator (green dot + "Saved" text)
  2. Take a snapshot to verify content structure
  3. Optionally take a screenshot for visual verification

The editor auto-saves, so no explicit save action is needed.

Step 9: Report Completion

草稿已保存到 Substack。请在 Substack 中预览并手动发布。
Draft saved to Substack. Please preview and publish manually.

Post URL: https://{publication}.substack.com/publish/post/{postId}

Complete Example Flow

User: "把 /path/to/my-article.md 发布到 Substack"

0. Pre-process tables (if any)
   - Detect Markdown tables
   - Create styled HTML for each table
   - Render to screenshots (open in browser, screenshot, close tab)
   - Remove table Markdown from content
   - Note insertion positions

1. Read /path/to/my-article.md
   - Extract title from frontmatter or H1
   - Extract subtitle from frontmatter excerpt
   - Get full Markdown content (with tables removed)

2. Convert Markdown to HTML
   - Strip frontmatter
   - Use markdown.markdown() with ['tables', 'fenced_code']
   - Write to /tmp/substack_article.html

3. Navigate to Substack dashboard or new post

4. Check if logged in
   - If not, prompt user to login

5. Fill title and subtitle

6. Copy HTML to clipboard + Paste
   - python3 copy_to_clipboard.py html --file /tmp/substack_article.html
   - Click editor content area
   - Press Cmd+V

7. Insert table images at correct positions
   - For each table: click position → Image button → Image menuitem → file upload

8. Verify draft saved
   - Check "Saved" status

9. Report success
   - "草稿已保存,请手动预览并发布"

Critical Rules

  1. NEVER click "Continue" - This starts the publish flow. Only save as draft (auto-save handles this)
  2. Always convert to HTML first - Plain Markdown will not be parsed by the Tiptap editor
  3. Use clipboard paste - The only reliable way to insert formatted content
  4. Check login status - Prompt user to login if needed
  5. Preserve original file - Never modify the source Markdown file
  6. Report completion - Tell user the draft is saved and needs manual review
  7. No nl2br extension - Causes double line breaks
  8. Tables → images - Pre-process tables before pasting content; upload images after paste
  9. Playwright file paths - Playwright MCP restricts file uploads to allowed roots; copy temp files to project directory before uploading
  10. Resolve publication first - Never hardcode a publication subdomain. Resolve via skill argument, SUBSTACK_PUBLICATION env var, or user prompt before any navigation (see Publication Resolution)
  11. Prefer Playwright MCP - Default to Playwright MCP; only use Chrome DevTools MCP when Playwright is unavailable

Troubleshooting

Content Shows as Plain Text (No Formatting)

If you see raw HTML tags or unformatted text:

  • Cause: Content was inserted using fill tool instead of clipboard paste
  • Solution: Use the copy_to_clipboard.py + Cmd+V method (see Step 6)

Tables Not Rendering (Shows Plain Text)

Substack's Tiptap editor does not support HTML tables. They collapse into inline plain text.

  • Solution: Convert tables to PNG via diagram-to-image skill → upload as images (see Step 0 and Step 7)
  • Alternative: Restructure simple tables as formatted lists
  • If plain text already pasted: Triple-click the plain text paragraph to select it, press Backspace to delete, then insert the table image at that position

Login Required

If page shows login prompt:

请先登录 Substack: https://{publication}.substack.com
登录完成后告诉我。

Editor Not Loading

If editor elements are not visible:

  1. Wait for page to fully load
  2. Take a new snapshot
  3. If still not loading, refresh the page

Clipboard Copy Fails

If copy_to_clipboard.py fails:

  • Ensure dependencies: pip install pyobjc-framework-Cocoa (macOS)
  • Check the HTML file exists and is readable
  • Try copying a smaller test string first

Element Reference

ElementSelector/IdentifierDescription
Title inputtextbox "title"Post title
Subtitle inputtextbox "Add a subtitle…"Post subtitle
Content area.ProseMirror (Tiptap editor)Post content
Save statusbutton "Saved"Auto-save indicator
Preview buttonbutton "Preview"Preview post
Continue buttonbutton "Continue"DO NOT USE - starts publish flow
Settings buttonbutton "Settings"Open settings sidebar
Exit buttonbutton "Exit"Exit editor
Image buttonbutton "Image"Opens image upload dropdown
Image menuitemmenuitem "Image"Opens file chooser for image upload
Author buttonbutton "{PublicationName}"Author/publication selector

Technical Details

Editor Stack

  • Tiptap: A headless, framework-agnostic rich-text editor built on ProseMirror
  • ProseMirror: The underlying rich-text editing framework
  • Paste handling: Tiptap natively parses HTML from clipboard and converts to its internal document model

Content Conversion Pipeline

Markdown file
    ↓ (Python markdown library)
HTML string
    ↓ (copy_to_clipboard.py)
System clipboard (text/html + text/plain)
    ↓ (Cmd+V keyboard shortcut)
Tiptap ProseMirror editor
    ↓ (auto-save)
Substack draft

Supported Formatting

The following Markdown elements are correctly rendered after HTML conversion and paste:

Markdown ElementSubstack SupportNotes
Headings (H2-H6)YesH1 not recommended (title is separate)
Bold / ItalicYes
Inline codeYes
Code blocksYesSyntax highlighting may vary
LinksYes
BlockquotesYes
Bullet listsYes
Ordered listsYes
Horizontal rulesYes
TablesNo → ImageConvert via diagram-to-image skill, upload as image
ImagesManualUpload via Image toolbar button → file chooser

FAQ

What does the publish-substack-article skill do?

Publish Markdown articles to Substack as drafts. Use when user wants to publish a Markdown file to Substack, or mentions "发布到 Substack", "Substack article", "publish to Substack". Handles Markdown-to-HTML conversion and saves as draft (never auto-publish).

How do I install publish-substack-article?

Run npx skills add https://github.com/sugarforever/01coder-agent-skills --skill publish-substack-article in your project. The skills CLI detects the agents you use and copies the skill into the right folder. For Claude Code you can also place the skill folder at ~/.claude/skills/publish-substack-article/ (personal) or .claude/skills/publish-substack-article/ (project).

Which AI agents can use publish-substack-article?

Any agent that supports the open Agent Skills format (a folder with a SKILL.md file), including Claude Code, Codex, Cursor, GitHub Copilot, Gemini CLI, Windsurf, Cline, OpenCode and more.

Is publish-substack-article safe to install?

No partner security audit is available yet. Skills can run commands and scripts, so read the SKILL.md and any bundled files before installing.

Who maintains publish-substack-article?

It is published by sugarforever in sugarforever/01coder-agent-skills. It has 605 recorded installs.

More from sugarforever/01coder-agent-skills

View all →

china-stock-analysis

sugarforever/01coder-agent-skills

A股价值投资分析工具,提供股票筛选、个股深度分析、行业对比和估值计算功能。基于价值投资理论,使用akshare获取公开财务数据,适合低频交易的普通投资者。

Business & Finance12.5K installs

next.js security scan

sugarforever/01coder-agent-skills

Comprehensive security vulnerability scanner for Next.js and TypeScript/JavaScript projects. Detects OWASP Top 10 vulnerabilities, XSS, injection flaws, authentication issues, hardcoded secrets, and Next.js-specific security problems. Audits dependencies for known CVEs and generates actionable security reports.

Security554 installs

python security scan

sugarforever/01coder-agent-skills

Comprehensive security vulnerability scanner for Python projects including Flask, Django, and FastAPI applications. Detects OWASP Top 10 vulnerabilities, injection flaws, insecure deserialization, authentication issues, hardcoded secrets, and framework-specific security problems. Audits dependencies for known CVEs and generates actionable security reports.

Security541 installs

subtitle-correction

sugarforever/01coder-agent-skills

Correct subtitle files (.srt) generated from speech recognition. Use when the user uploads subtitle files and asks to correct, fix, or proofread subtitles, especially for technical content like programming tutorials, AI/ML courses, or any content with domain-specific terminology. Supports Chinese and English subtitles with intelligent error detection and correction while preserving exact timeline information.

AI & Agents537 installs

diagram-to-image

sugarforever/01coder-agent-skills

Convert Mermaid diagrams and Markdown tables to images (PNG) for platforms that don't support rich formatting. Use when user asks to "convert to image", "export as PNG", "make this an image", or has content for X/Twitter that needs visual exports.

Media & Creative509 installs

video-script

sugarforever/01coder-agent-skills

Create video scripts and publishing materials for YouTubers/UP主. Use when user wants to prepare a video, write a script (口播稿), generate video title, description, tags, or chapter timestamps. Triggers on "写视频脚本", "视频口播稿", "video script", "prepare video", "视频发布素材", or mentions creating content for YouTube/Bilibili.

Media & Creative484 installs

Popular docs & writing skills

View category →

grill-with-docs

mattpocock/skills

A relentless interview to sharpen a plan or design, which also creates docs (ADR's and glossary) as we go.

Docs & Writing1.2M installs

lark-markdown

open.feishu.cn

飞书 Markdown:查看、创建、上传、编辑和比较飞书中的原生 Markdown 文件。当用户要操作飞书 Markdown 文件,或比较其远端版本及本地草稿时使用。纯本地 Markdown 文件操作不触发本 skill。不负责将 Markdown 导入为飞书在线文档,也不负责文件搜索、权限、评论、移动、删除等云空间管理操作。

Docs & Writing778.7K installs

writing-beats

mattpocock/skills

Writing, exploit; assemble raw material into a journey of beats, grounding each term before a beat leans on it.

Docs & Writing490.7K installs

writing-shape

mattpocock/skills

Writing, exploit: shape raw material into an article, paragraph by paragraph.

Docs & Writing490.2K installs

writing-fragments

mattpocock/skills

Writing, explore: mine raw fragments, no structure yet.

Docs & Writing489.6K installs

writing-for-agents

mattpocock/skills

Writing documents for agents. Use when creating or editing skills, or modifying AGENTS.md or CLAUDE.md.

Docs & Writing479.3K installs