DEV Community

Fernando Paladini
Fernando Paladini

Posted on

Build a Draft-First Medium Publishing Agent with MCP

Publishing the same technical article on more than one platform sounds like a copy-and-paste task. In practice, the risky part is deciding what an AI agent is allowed to do with an authenticated browser session.

This tutorial builds a draft-first Medium workflow with publish-agents, an open-source TypeScript project by Fernando Paladini. The @paladini/medium-publisher-mcp@ package exposes Medium publishing operations through the Model Context Protocol, while keeping login, import, review, and live publication as separate decisions.

TL;DR

Install the stable v0.2.3 package, log in once, register its stdio MCP server, and ask your MCP client to import a public DEV.to article with publish: false. The agent can prepare and inspect a draft, but you retain approval for browser authentication, formatting changes, and publication.

What you will build

The resulting flow has four boundaries:

  1. A public DEV.to article provides the source Markdown.
  2. The local MCP server exposes publishing tools to an AI client.
  3. Patchright uses a saved browser session to work in Medium.
  4. A human reviews the real editor before any live publication.

MCP standardizes how a host connects to tools, but it does not make a tool safe by itself. The protocol documentation describes tools as capabilities that need consent and careful authorization. That is why this example uses a draft as the default outcome.

Prerequisites

You need:

  • Node.js 20 or newer.
  • An MCP-compatible client such as Cursor or Claude Code.
  • A Medium account that can create stories.
  • A published DEV.to article with a public URL.
  • Permission to install the bundled Chromium browser locally.

The tested release reference is v0.2.3. The package declares @paladini/medium-publisher-mcp version 0.2.3, requires Node.js >=20, uses Patchright, and is licensed under MIT.

Install the stable package

The shortest path installs the published npm package:

npm install -g @paladini/medium-publisher-mcp
Enter fullscreen mode Exit fullscreen mode

The package exposes two commands: medium-publisher for the CLI and medium-publisher-mcp for the MCP server. Its post-install script requests the Patchright Chromium browser. If your package manager skipped that step, follow the Patchright installation documentation before trying browser automation.

You can verify that the CLI is on your PATH without logging in:

medium-publisher --help
Enter fullscreen mode Exit fullscreen mode

If you prefer a source checkout, the stable repository documents this equivalent path:

git clone https://github.com/paladini/publish-agents.git
cd publish-agents
git checkout v0.2.3
npm install
npm run build -w @paladini/medium-publisher-mcp
npm link -w @paladini/medium-publisher-mcp
Enter fullscreen mode Exit fullscreen mode

Create the local browser session

Login is interactive because the tool needs a Medium session, not a Medium API key:

medium-publisher login
Enter fullscreen mode Exit fullscreen mode

The documented Windows default is %LOCALAPPDATA%\\medium-publisher\\storageState.json. The package also supports MEDIUM_STATE_PATH and MEDIUM_PUBLISHER_HOME when you need a different local location.

Check the session in structured output:

medium-publisher session-check --json
Enter fullscreen mode Exit fullscreen mode

Treat storageState.json like a credential store. Do not commit it, attach it to an issue, or place it in a CI artifact. A local browser profile can contain cookies that grant access to your Medium account.

Register the MCP server

The server uses stdio. A minimal MCP configuration is:

{
  "mcpServers": {
    "medium-publisher": {
      "command": "medium-publisher-mcp"
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

The repository includes client-specific setup notes for Cursor and Claude Code. After registering the server, restart or reload the client so it discovers the tools.

The package exposes seven tools in the documented release. The important ones for this workflow are medium_session_check, medium_publish_from_devto, medium_extract, medium_fix_draft, and medium_open_draft.

Import a DEV.to article as a draft

Ask the MCP client to call the server with a public article URL and an explicit non-publishing instruction:

{
  "devto_url": "https://dev.to/your-name/your-tutorial",
  "publish": false
}
Enter fullscreen mode Exit fullscreen mode

This maps to the medium_publish_from_devto tool. The package fetches the published DEV.to representation, opens Medium's official /p/import route, waits for the editor to save, and returns JSON containing a Medium URL plus details such as title, subtitle, topics, and hero-image detection.

The false value is the critical control. It asks the pipeline to stop at a draft. Do not treat a successful tool response as proof that the article is ready for live publication. Open the returned editor URL and inspect it.

Review the actual editor

The source Markdown is the ground truth, but the Medium editor is the thing readers will see. Check:

  • The title and subtitle match the source.
  • Headings are rendered as headings rather than literal Markdown.
  • Each code example remains one code block with readable content.
  • Lists, links, and paragraphs keep their order.
  • Topics describe the article without adding unsupported claims.
  • No secret, private URL, or unexpected text crossed the import boundary.

The package's extraction command reports a structured outline and formatting flags:

medium-publisher extract --url "https://medium.com/p/your-draft/edit" --json
Enter fullscreen mode Exit fullscreen mode

Pay special attention to content after the first code block. A malformed import can make later paragraphs look like code, which is easy to miss during a quick visual scan.

Apply a targeted correction

If extraction identifies a known issue, create a small actions file and apply only the required fixes:

[
  { "type": "removeEmptyCodeBlocks" },
  { "type": "mergeAdjacentCodeBlocks" },
  { "type": "promoteDemoteHeading", "blockIndex": 4, "level": 2 }
]
Enter fullscreen mode Exit fullscreen mode

Run the correction against the same draft:

medium-publisher fix-draft --url "https://medium.com/p/your-draft/edit" --actions-file fixes.json --json
Enter fullscreen mode Exit fullscreen mode

Then extract again and compare the affected blocks with the DEV.to source. Do not repeatedly apply actions when the editor structure has changed. A selector or block index that was correct for one draft can be wrong after an import or a Medium UI change.

Verify success before publishing

A useful verification record contains:

  1. The source DEV.to URL.
  2. The returned Medium draft URL.
  3. The extraction result before any fix.
  4. The actions file, if a fix was needed.
  5. The extraction result after the fix.
  6. A human check of title, code, links, metadata, and sensitive data.

Only after those checks should you decide whether to use a live publication option. The project also provides a CLI path with --publish, but this tutorial deliberately does not include it in the working command. Keeping the approval step outside the default agent request makes accidental publication less likely.

Why this design works

The workflow separates three kinds of state that are often conflated:

  • Source state: the published DEV.to Markdown.
  • Review state: the imported Medium draft and its extracted blocks.
  • Public state: the final Medium story after an explicit publication action.

That separation makes failures diagnosable. If the import is wrong, you can fix or discard the draft without changing the source. If the session expires, you can log in again without changing the article. If Medium changes its UI, you can stop at review instead of trusting an unattended browser run.

Failure modes and security boundaries

The session is missing or expired

The CLI documents exit code 3 for a missing or expired session. Run medium-publisher login again. Two-factor authentication or a bot challenge may require headed interaction.

The import does not complete

The DEV.to URL must be public. Check it in a private browser window and confirm that its public API representation contains the article body. An authenticated or unpublished source is not a reliable import input.

Medium selectors stop matching

Medium's interface can change. The repository specifically targets a contenteditable import field rather than assuming the first visible HTML input is the article URL field. If the workflow behaves unexpectedly, inspect the draft manually and report the reproducible case instead of publishing.

Sensitive data appears in the draft

Stop immediately. Do not publish the draft. Remove it through Medium's controls as appropriate, inspect the local browser-state directory, and rotate any credential that may have been exposed. The project's security check can flag secret-like values and unexpected short links, but it cannot replace human review.

FAQ

Does MCP publish automatically?

MCP only provides the connection between the client and the server. The server's publication capabilities still require authorization. Use publish: false for the draft-first workflow.

Do I need a Medium API key?

No. The documented package uses browser automation and a saved login session because it does not depend on a stable public Medium write API.

Can I use a personal blog instead of DEV.to?

The package supports importing public URLs generally, but the DEV.to pipeline can compare the imported result with DEV.to source Markdown. That comparison is the most useful part of this tutorial's verification loop.

Is the workflow production-proof?

No. Browser UI changes, account challenges, content-specific formatting, and authentication failures remain possible. The project reduces risk with extraction, targeted fixes, security checks, and draft-first behavior; it does not guarantee a successful unattended publication.

AI assistance disclosure

AI assistance was used to organize and edit this tutorial. The release, commands, MCP configuration, session paths, limitations, and security boundaries were checked against the public publish-agents repository at v0.2.3, its package documentation, and the current MCP specification.

Takeaway

An MCP publishing agent is most useful when it prepares reviewable state instead of silently crossing the publication boundary. Install a pinned release, save the browser session locally, import with publish: false, compare the Medium editor with the source, and keep live publication explicit.

What is the most valuable approval checkpoint in your own agent-assisted publishing workflow: authentication, content review, metadata review, or the final publish action?

Top comments (0)