Medium Reader MCP

May 10, 2026 · View on GitHub

A Model Context Protocol server for reading and downloading Medium articles as Markdown. This MCP server enables AI assistants like Claude Desktop, Claude Code, OpenAI Codex, and Gemini CLI to fetch a single Medium post by URL, summarize it in chat, or archive it (Markdown + images) to disk.

License: MIT GitHub Repository

Powered by ZMediumToMarkdown (Ruby gem ≥ 3.5.2) — the gem owns every Medium fetch, every cookie, every Markdown render. mcp-medium-reader is a thin MCP wrapper around it.

Overview

The LLM auto-discovers when to use this server: as soon as the user pastes a medium.com URL (or a Medium custom-domain URL like towardsdatascience.com) or asks to read / summarize / archive an article, the server's tools take over. Supported on macOS, Linux, and Windows.

What you get instead of the usual "LLM tries to fetch the URL itself" failure mode:

  • Clean Markdown, not HTML. Frontmatter (title / author / date / tags), every paragraph, every blockquote, every outbound link, and every miro.medium.com image URL come back verbatim — no React/JSON noise, no ads, no nav chrome. The LLM can quote, translate, summarize, or rewrite from the real article instead of a lossy preview.
  • Full depth, not a summary. Built-in fetchers typically collapse a long post to a few hundred tokens and drop the prices / schedules / names that make the article useful. This server returns the whole body — see the Before / After table below for a 47 KB vs. 1.5 KB head-to-head on the same article.
  • Token-efficient. ~17K tokens of pure article body for a 6,500-word post, versus 100K+ tokens of HTML soup if you feed the raw page in.
  • Reliable past Cloudflare. Direct datacenter requests to Medium get a 403; this server delegates to the underlying gem, which goes through Medium's API with optional cookie / Worker-proxy support.
  • Paywall-aware. Once you sign in once via the gem CLI, member-only posts come through too. Cookies stay in the gem's AES-256-GCM cache, never on the MCP wire.
  • Disk archive on demand. download_medium_post saves Markdown plus all referenced images locally; re-runs are skip-if-unchanged.

URL recognition covers medium.com, *.medium.com, and Medium publications on custom domains (towardsdatascience.com, betterhumans.pub, uxdesign.cc, levelup.gitconnected.com, …). The 12-char hex slug suffix (-a1b2c3d4e5f6) is the canonical Medium tell across hosts.

Why MCP — Before / After

Same article tested both ways: https://medium.com/p/055527a739dd (a long-form travel post, ~6,500 words, 180+ images).

AspectWithout MCP (URL fed to LLM directly)With mcp-medium-reader
ReachabilityMedium returns 403 Cloudflare block to direct datacenter requests; built-in fetchers usually fall back to a forced short summaryGoes through the ZMediumToMarkdown gem against Medium's API (cookies / Worker proxy optional), reliably returns full body
Content depth~1.5 KB summary — prices, schedules, restaurant and hotel names all dropped; impossible to translate, quote, or post-process47 KB of clean Markdown: frontmatter (title / author / date / tags) + every paragraph + image URLs + outbound links + blockquotes preserved verbatim
Token usageLooks cheap (~600 tokens) but the information is already destroyed — no follow-up task possible. Feeding the raw HTML instead balloons to 100K+ tokens of React/JSON noise~17K tokens, all article body, zero HTML / JS / ad noise
PaywallNo pathGem manages its own AES-256-GCM cookie cache; full member-only content readable after one-time login

Bottom line: without MCP the LLM receives either a lossy summary or a wall of HTML noise; with MCP it receives clean Markdown it can actually work with.

Installation

bash <(curl -fsSL https://raw.githubusercontent.com/ZhgChgLi/mcp-medium-reader/main/setup.sh)

Or download and run locally:

curl -fsSL https://raw.githubusercontent.com/ZhgChgLi/mcp-medium-reader/main/setup.sh -o setup.sh
bash setup.sh

setup.sh is cross-platform (macOS / Linux / Windows-Git-Bash). It checks Ruby ≥ 3.2 and Node ≥ 18 (offers brew install automatically on macOS), installs the ZMediumToMarkdown gem and the mcp-medium-reader npm package, runs mcp-medium-reader init to bind a stable Ruby runtime and register the server with Claude Desktop, Claude Code, OpenAI Codex, and Gemini CLI, prints doctor so you can confirm everything is wired up, then walks you through ZMediumToMarkdown --auth so the gem cookie cache is populated in the same session. Restart your MCP clients afterwards.

Flags: --no-init (deps only) / --no-gem / --no-npm / --no-auth (skip the interactive ZMediumToMarkdown --auth) / --yes (assume yes to brew/install prompts) / --help.

Manual Installation

# 1. Install the Ruby gem (the engine that actually fetches Medium)
gem install ZMediumToMarkdown      # must be >= 3.5.2

# 2. Install this MCP server
npm install -g mcp-medium-reader

# 3. One-shot setup: dependency check + Ruby runtime bind + register with all MCP clients
mcp-medium-reader init

Verify everything is in place:

mcp-medium-reader doctor

First-time Login (one-off, optional)

If you only ever read public posts, you can skip this step. For paywalled posts, run the gem standalone once in your own Terminal so it can open Chrome for sign-in / Cloudflare clearance and cache the cookies in ~/.zmediumtomarkdown:

ZMediumToMarkdown --auth

(setup.sh runs this for you automatically at the end; pass --no-auth to skip.) After that, every MCP tool call uses the cache automatically. The MCP transport gives the gem a non-TTY stdin (which trips its --non-interactive auto-detection), so the gem cannot drive its own Chrome login under MCP — the first-time sign-in has to happen in a real Terminal.

Terms of Use note (ZMediumToMarkdown ≥ 4.0.0)

ZMediumToMarkdown 4.0.0 introduced a first-run Terms-of-Use gate. Because MCP runs the gem on a non-TTY stream, mcp-medium-reader sets ZMTM_TOS_ACCEPTED=1 in the gem's environment automatically — installing this server is acceptance of the gem's Terms on your behalf. If that's not what you want, don't install this MCP server; run the gem yourself in a Terminal and respond to the prompt interactively. Read the full Terms at the link above before deciding.

Quickstart

After install + restarting your MCP client, just paste a Medium URL into the chat:

Read https://medium.com/zrealm-ios-dev/a-milestone-of-1-000-followers-on-medium-6fd11e9704f2 and summarize the three biggest takeaways.

The LLM picks read_medium_post automatically and answers from the full article — not a Cloudflare error, not a 1.5 KB preview.

Other prompts that auto-route to this server:

  • "Translate this Medium post to English: <url>"
  • "Save this article as Markdown to ~/Documents/articles: <url>" — uses download_medium_post
  • "Is my Medium reader setup working?" — uses validate_setup

If the LLM doesn't pick the tool, restart the MCP client (the registration is read at startup) or run mcp-medium-reader doctor to verify the install.

Integration with MCP Clients

mcp-medium-reader install adds the server registration to the supported clients automatically. Restart each client afterwards.

ClientConfig File
Claude Desktop~/Library/Application Support/Claude/claude_desktop_config.json (macOS) / %APPDATA%\Claude\claude_desktop_config.json (Windows) / ~/.config/Claude/claude_desktop_config.json (Linux)
Claude Code~/.claude.json
OpenAI Codex~/.codex/config.toml
Gemini CLI~/.gemini/settings.json

Limit which clients to install for:

mcp-medium-reader install --clients=claude-desktop,gemini

Available IDs: claude-desktop, claude-code, codex, gemini.

The registered server name is medium-reader (so it shows up in the LLM tool-call UI as medium-reader.read_medium_post, etc.).

Manual Configuration

If you'd rather edit config files yourself, the entry is:

"medium-reader": {
  "command": "mcp-medium-reader"
}

For Codex (config.toml):

[mcp_servers.medium-reader]
command = "mcp-medium-reader"

Available Tools

read_medium_post

Fetch a single Medium post by URL → Markdown in chat.

Parameters:

  • url: Medium post URL. Accepts medium.com, *.medium.com, and Medium publications on custom domains (URLs ending in a 12-char hex slug suffix like -a1b2c3d4e5f6 are Medium regardless of host).

If the post is paywalled, the response prepends a step-by-step recovery script the LLM can follow.

download_medium_post

Download a single Medium post (Markdown + images) to disk.

Parameters:

  • url: Medium post URL.
  • output_dir (optional): Directory to write into. Files land under <output_dir>/Output/zmediumtomarkdown/. Accepts ~/.... Defaults to the current working directory of the MCP server process.

Re-runs are skip-if-unchanged.

validate_setup

Inspect the dependency state and (optionally) run a live test request.

Parameters:

  • test_url (optional): A Medium post URL to read with the current gem cookie cache. Reports whether full content was returned vs. a paywall preview.

Configuration

Environment Variables

VariableDescriptionRequired
MCP_MEDIUM_READER_CONFIG_DIROverride config directory (defaults to $XDG_CONFIG_HOME/mcp-medium-reader on POSIX, %APPDATA%\mcp-medium-reader on Windows). Holds runtime.json.No
ZMEDIUM_COOKIE_CACHE_PATHOverride the gem cookie cache path probed by doctor (defaults to ~/.zmediumtomarkdown).No

There are no credentials env vars — Medium cookies belong to the gem, not the MCP server.

What's Stored Where

DataLocationOwner
Medium login cookies (sid, uid, cf_clearance, _cfuvid)~/.zmediumtomarkdown (AES-256-GCM, mode 0600)ZMediumToMarkdown gem
Ruby runtime pin (rubyPath, GEM_HOME, GEM_PATH)<config_dir>/runtime.json (mode 0600 on POSIX)mcp-medium-reader

Subcommands

mcp-medium-reader [serve]         Start MCP stdio server (default).
mcp-medium-reader init            Guided setup: dep check + Ruby runtime bind + MCP client install.
mcp-medium-reader install [...]   Add server entry to MCP client configs only.
                                  Optional --clients=<id,id,...>.
                                  IDs: claude-desktop, claude-code, codex, gemini.
mcp-medium-reader doctor          Print platform / dependency / runtime / cookie-cache status.
mcp-medium-reader help            Show help.
mcp-medium-reader version         Show version.

Troubleshooting

Gem not found / wrong Ruby selected

ZMediumToMarkdown gem not found
or: Configured Ruby runtime is invalid

Install the gem and bind a stable Ruby runtime:

gem install ZMediumToMarkdown      # or: gem update ZMediumToMarkdown
mcp-medium-reader init             # rebinds runtime.json from the active shell

Run init from a shell where both ruby and the gem work, so the bound rubyPath / GEM_HOME are valid.

Gem version too old

ZMediumToMarkdown X.Y.Z is too old
gem update ZMediumToMarkdown

mcp-medium-reader requires gem version 3.5.2 or later.

Paywalled post returns only a public preview

The gem's cookie cache is missing or stale. Run the gem standalone once in your own Terminal:

ZMediumToMarkdown --auth

Chrome opens for sign-in; the gem caches sid / uid / cf_clearance / _cfuvid in ~/.zmediumtomarkdown. Then ask the AI again. Use validate_setup({ test_url: "..." }) to confirm cookies grant access.

Cloudflare blocked the request

mcp-medium-reader runs the gem in non-interactive mode, so the gem cannot open its own browser to clear the challenge under MCP. Two-step recovery — the tool response will walk the LLM through this automatically:

  1. First block this session: ask the user to run ZMediumToMarkdown --auth once in a Terminal so the gem refreshes cf_clearance / _cfuvid in its cache.

  2. Repeat blocks: drop MCP and run the gem CLI directly in a Terminal — there it has a real TTY and can re-drive Chrome interactively:

    ZMediumToMarkdown -p <post-url>
    

    Worker proxy / cookie setup guide: https://github.com/ZhgChgLi/ZMediumToMarkdown/blob/main/wiki/Setting-Up-Medium-Cookies-and-a-Cloudflare-Worker-Proxy.md

Development

Prerequisites

  • Node.js 18+
  • Ruby 3.2+
  • ZMediumToMarkdown gem 3.5.2+

Setup

# Clone the repository
git clone https://github.com/ZhgChgLi/mcp-medium-reader.git
cd mcp-medium-reader

# Install dependencies
npm install

# Build the project
npm run build       # tsc → dist/

# Run tests
npm test            # vitest

# Run via tsx without building
npm run dev -- doctor

License

This project is licensed under the MIT License — see the LICENSE file for details.


Disclaimer

All content fetched or downloaded through mcp-medium-reader (which is powered by ZMediumToMarkdown) — articles, images, video — is subject to copyright and belongs to its respective owner. This tool does not claim ownership of any fetched or downloaded content.

Fetching, downloading, or otherwise using copyrighted content without the owner's permission may be illegal. Neither mcp-medium-reader nor ZMediumToMarkdown condones copyright infringement, and neither will be held responsible for misuse of these tools. Users are solely responsible for ensuring they have the necessary permissions and rights for any content they fetch or download.

By using mcp-medium-reader you acknowledge and agree to comply with all applicable copyright laws and regulations, as well as Medium's Terms of Service.