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.
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.comimage 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_postsaves 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).
| Aspect | Without MCP (URL fed to LLM directly) | With mcp-medium-reader |
|---|---|---|
| Reachability | Medium returns 403 Cloudflare block to direct datacenter requests; built-in fetchers usually fall back to a forced short summary | Goes 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-process | 47 KB of clean Markdown: frontmatter (title / author / date / tags) + every paragraph + image URLs + outbound links + blockquotes preserved verbatim |
| Token usage | Looks 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 |
| Paywall | No path | Gem 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
Quick Install (Recommended)
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>" — usesdownload_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.
| Client | Config 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. Acceptsmedium.com,*.medium.com, and Medium publications on custom domains (URLs ending in a 12-char hex slug suffix like-a1b2c3d4e5f6are 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
| Variable | Description | Required |
|---|---|---|
MCP_MEDIUM_READER_CONFIG_DIR | Override 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_PATH | Override 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
| Data | Location | Owner |
|---|---|---|
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:
-
First block this session: ask the user to run
ZMediumToMarkdown --authonce in a Terminal so the gem refreshescf_clearance/_cfuvidin its cache. -
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+
ZMediumToMarkdowngem 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.
Links
- GitHub Repository
- ZMediumToMarkdown gem — the Ruby gem that does the heavy lifting
- Model Context Protocol
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.