OpenPub
Open source · MIT · MCP server included

Publish articles to eight platforms from Claude Code, Codex, Cursor or your terminal.

Write in Markdown once. OpenPub sends it everywhere as a draft, and nothing goes live until you say so.

Local stdio server · Node 20+

1Build the server

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:build

2Add it to Claude Code

Merge 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"
      ]
    }
  }
}

3Restart and ask

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.
Full setup, tools and options in the docs →
How it works

One Markdown file. Check the plan before you publish.

01

Write in Markdown

Frontmatter picks the platforms, tags and status. The file stays in your repo.

02

Check the plan

A dry run, from you or your agent, shows what each platform will do before anything is sent.

03

Publish and keep the record

Canonical URLs are wired, and every result lands in a ledger you own.

Platform capabilities

Every platform, with its real limits.

Not every API can do everything. OpenPub says what each one supports before you publish, and never duplicates a post to hide a gap.

Create and update in place

An edit updates the existing post once OpenPub has stored its remote id, or its file path for GitHub Pages.

dev.toGhostWordPressGitHub PagesHashnode

Hashnode drafts need a Pro publication, and a live Hashnode post can’t go back to draft.

Create only

These APIs can’t edit a post. A later edit is skipped with a warning, so you never get a second copy.

MediumX Articles

Medium’s API is deprecated and works only with existing tokens. X Articles requires X Premium.

Draft only

No public API. OpenPub creates the draft through the endpoint the web editor uses; you publish from Substack itself.

Substack

Editing a draft OpenPub already created is skipped with a warning.

Features

What happens after you publish.

8 platforms, one adapter each

Each platform gets its own adapter, and each result is reported on its own.

dev.toPublished
HashnodeDraft
WordPressDraft
GhostPublished
MediumCreated
SubstackDraft
GitHub PagesPublished
X ArticlesDraft

Drafts by default

A post goes live only when you set status to published.

CLI, no flagDraft
Agent over MCPDraft
--status publishedPublished

Canonical URLs wired

The first public URL becomes the canonical for the rest. Or pin one with canonical_url.

GitHub PagesPrimary
dev.toCanonical → primary
HashnodeCanonical → primary

Publish log and retries

Network and 5xx failures are retried; 4xx errors fail. One failure never stops the others.

dev.to201 · Published
Hashnode503 · Retrying
Medium401 · Failed
Ghost200 · Updated

Idempotent republish

Unchanged posts are skipped. Edits update the post where the platform allows it.

Unchanged fileSkipped
Edited, GhostUpdated in place
Edited, MediumSkipped, create only

Self-host on Cloudflare

The optional console and API run as a Worker in your own account. How to deploy

WorkerD1KVR2Queues
Use cases

Three ways to publish with it.

Same engine every time. Pick the entry point that fits how you already write.

CLI

Own the post, cross-post the rest

For writers with their own blog. The post lives on your site; the copies point back to it.

  • Set primary: github-pages in frontmatter
  • Canonical URLs are wired for you
node apps/cli/dist/index.js publish post.md --dry-run
MCP server

Let your agent ship the draft

For people who write with Claude Code, Codex, Cursor or Copilot. The agent checks links, dry-runs and drafts.

  • Same engine as the CLI
  • Nothing goes live without your say
Check the links in post.md, then do a dry run to dev.to and Hashnode.
Web console

One console for a small team

For teams that share platform accounts. Review drafts, publish and read the log in a console you host.

  • Runs on your Cloudflare account
  • Publish log for every platform
npm run deploy
Markdown preview

Frontmatter in, article out.

Frontmatter sets the title, tags, platforms and status. The body is rendered once and sent to each platform, which applies its own styles.

posts/markdown-pipeline.mdSample content · nothing is sent
Markdown
---
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-run
Preview

Shipping a Markdown pipeline

markdownpublishingtooling

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-run
Questions

Before you try it.

Which agents can use it?

Any 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.

Do I need to host anything?

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.

Where do my credentials go?

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.

Can it publish something by accident?

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.

Is the Cloudflare deploy one click?

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.

Self-host

Run it on your own Cloudflare 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.