8 platforms, one adapter each
Each platform gets its own adapter, and each result is reported on its own.
Write in Markdown once. OpenPub sends it everywhere as a draft, and nothing goes live until you say so.
Build from the repo once. No hosted service or account needed.
git clone https://github.com/princepal9120/openpub
cd openpub
npm install
npm run mcp:buildMerge this into .mcp.json. Replace the absolute path with your own.
{
"mcpServers": {
"openpub": {
"command": "node",
"args": [
"/absolute/path/to/openpub/apps/mcp/dist/index.js"
]
}
}
}Restart your client. It can store a key, check links, dry-run and publish. Drafts stay the default.
Check the links in post.md, then do a dry run to dev.to and Hashnode.Frontmatter picks the platforms, tags and status. The file stays in your repo.
A dry run, from you or your agent, shows what each platform will do before anything is sent.
Canonical URLs are wired, and every result lands in a ledger you own.
Not every API can do everything. OpenPub says what each one supports before you publish, and never duplicates a post to hide a gap.
An edit updates the existing post once OpenPub has stored its remote id, or its file path for GitHub Pages.
Hashnode drafts need a Pro publication, and a live Hashnode post can’t go back to draft.
These APIs can’t edit a post. A later edit is skipped with a warning, so you never get a second copy.
Medium’s API is deprecated and works only with existing tokens. X Articles requires X Premium.
No public API. OpenPub creates the draft through the endpoint the web editor uses; you publish from Substack itself.
Editing a draft OpenPub already created is skipped with a warning.
Each platform gets its own adapter, and each result is reported on its own.
A post goes live only when you set status to published.
The first public URL becomes the canonical for the rest. Or pin one with canonical_url.
Network and 5xx failures are retried; 4xx errors fail. One failure never stops the others.
Unchanged posts are skipped. Edits update the post where the platform allows it.
The optional console and API run as a Worker in your own account. How to deploy
Same engine every time. Pick the entry point that fits how you already write.
For writers with their own blog. The post lives on your site; the copies point back to it.
node apps/cli/dist/index.js publish post.md --dry-runFor people who write with Claude Code, Codex, Cursor or Copilot. The agent checks links, dry-runs and drafts.
Check the links in post.md, then do a dry run to dev.to and Hashnode.For teams that share platform accounts. Review drafts, publish and read the log in a console you host.
npm run deployFrontmatter sets the title, tags, platforms and status. The body is rendered once and sent to each platform, which applies its own styles.
---
title: "Shipping a Markdown pipeline"
tags: [markdown, publishing, tooling]
platforms: [devto, hashnode, github-pages]
primary: github-pages
status: draft
---
Every post starts as a file in my repo. From there
it fans out, and the file stays the source of truth.
## What the pipeline does
- Parses frontmatter for title and tags
- Renders the body once
- Wires the canonical URL back to my site
> Write once, then stop copying and pasting.
node apps/cli/dist/index.js publish post.md --dry-runEvery post starts as a file in my repo. From there it fans out, and the file stays the source of truth.
Write once, then stop copying and pasting.
node apps/cli/dist/index.js publish post.md --dry-runAny MCP client that runs a local stdio server, including Claude Code, Codex, Cursor, GitHub Copilot in VS Code, OpenCode and Hermes Agent. Your agent gets the same publishing engine as the CLI. Grok Bot setup has not been verified.
No. The CLI and MCP server run on your machine. The web console and API are optional, and you host them on your own Cloudflare account.
With the CLI and local MCP server, into ~/.openpub/config.json on your machine, with mode 0600. Credentials are sent only to the platform you publish to. Worker mode stores them in your own D1 database.
Draft is the default. Publishing requires --status published, status: published in frontmatter, or defaults.status in your config. A dry run shows the plan before sending anything. GitHub Pages drafts stay off the live feed.
No. Create D1, KV, R2 and a Queue, set your own resource IDs in apps/worker/wrangler.toml, run the migration, set ADMIN_KEY, then deploy. R2 and Queues may need enabling on your account.
The CLI and MCP server need nothing hosted. The optional console and API run as a Worker backed by D1, KV, R2 and Queues that you create. The docs walk through every command.