{"id":51045319,"url":"https://github.com/zhgchgli/mcp-medium-reader","last_synced_at":"2026-06-22T13:02:29.820Z","repository":{"id":356322115,"uuid":"1229077008","full_name":"ZhgChgLi/mcp-medium-reader","owner":"ZhgChgLi","description":"MCP server that reads Medium articles as clean Markdown — works past Cloudflare, supports paywalled posts, image archive on demand.","archived":false,"fork":false,"pushed_at":"2026-05-07T16:01:44.000Z","size":350,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-07T16:07:12.942Z","etag":null,"topics":["ai","claude","claude-ai","claude-code","claude-code-plugin","claude-skills","codex","gemini-cli","mcp","medium","medium-api","medium-article","medium-downloader","medium-reader","model-context-protocol"],"latest_commit_sha":null,"homepage":"https://link.zhgchg.li/","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ZhgChgLi.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-05-04T17:15:27.000Z","updated_at":"2026-05-07T15:17:47.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/ZhgChgLi/mcp-medium-reader","commit_stats":null,"previous_names":["zhgchgli/mcp-medium-reader"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/ZhgChgLi/mcp-medium-reader","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ZhgChgLi%2Fmcp-medium-reader","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ZhgChgLi%2Fmcp-medium-reader/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ZhgChgLi%2Fmcp-medium-reader/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ZhgChgLi%2Fmcp-medium-reader/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ZhgChgLi","download_url":"https://codeload.github.com/ZhgChgLi/mcp-medium-reader/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ZhgChgLi%2Fmcp-medium-reader/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34649822,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-22T02:00:06.391Z","response_time":106,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ai","claude","claude-ai","claude-code","claude-code-plugin","claude-skills","codex","gemini-cli","mcp","medium","medium-api","medium-article","medium-downloader","medium-reader","model-context-protocol"],"created_at":"2026-06-22T13:02:28.515Z","updated_at":"2026-06-22T13:02:29.812Z","avatar_url":"https://github.com/ZhgChgLi.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Medium Reader MCP\n\nA [Model Context Protocol](https://modelcontextprotocol.io/) 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.\n\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n[![GitHub Repository](https://img.shields.io/badge/GitHub-Repository-blue.svg)](https://github.com/ZhgChgLi/mcp-medium-reader)\n\n\u003e **Powered by [ZMediumToMarkdown](https://github.com/ZhgChgLi/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.\n\n## Overview\n\nThe 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**.\n\nWhat you get instead of the usual \"LLM tries to fetch the URL itself\" failure mode:\n\n- **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.\n- **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](#why-mcp--before--after) for a 47 KB vs. 1.5 KB head-to-head on the same article.\n- **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.\n- **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.\n- **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.\n- **Disk archive on demand.** `download_medium_post` saves Markdown plus all referenced images locally; re-runs are skip-if-unchanged.\n\nURL 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.\n\n## Why MCP — Before / After\n\nSame article tested both ways: [`https://medium.com/p/055527a739dd`](https://medium.com/p/055527a739dd) (a long-form travel post, ~6,500 words, 180+ images).\n\n| Aspect | Without MCP (URL fed to LLM directly) | With mcp-medium-reader |\n| --- | --- | --- |\n| **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** |\n| **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 |\n| **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 |\n| **Paywall** | No path | Gem manages its own AES-256-GCM cookie cache; full member-only content readable after one-time login |\n\nBottom 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.**\n\n## Installation\n\n### Quick Install (Recommended)\n\n```bash\nbash \u003c(curl -fsSL https://raw.githubusercontent.com/ZhgChgLi/mcp-medium-reader/main/setup.sh)\n```\n\nOr download and run locally:\n\n```bash\ncurl -fsSL https://raw.githubusercontent.com/ZhgChgLi/mcp-medium-reader/main/setup.sh -o setup.sh\nbash setup.sh\n```\n\n`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.\n\nFlags: `--no-init` (deps only) / `--no-gem` / `--no-npm` / `--no-auth` (skip the interactive `ZMediumToMarkdown --auth`) / `--yes` (assume yes to brew/install prompts) / `--help`.\n\n### Manual Installation\n\n```bash\n# 1. Install the Ruby gem (the engine that actually fetches Medium)\ngem install ZMediumToMarkdown      # must be \u003e= 3.5.2\n\n# 2. Install this MCP server\nnpm install -g mcp-medium-reader\n\n# 3. One-shot setup: dependency check + Ruby runtime bind + register with all MCP clients\nmcp-medium-reader init\n```\n\nVerify everything is in place:\n\n```bash\nmcp-medium-reader doctor\n```\n\n### First-time Login (one-off, optional)\n\nIf 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`:\n\n```bash\nZMediumToMarkdown --auth\n```\n\n(`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.\n\n### Terms of Use note (ZMediumToMarkdown ≥ 4.0.0)\n\nZMediumToMarkdown 4.0.0 introduced a [first-run Terms-of-Use gate](https://github.com/ZhgChgLi/ZMediumToMarkdown/blob/main/TERMS.md). 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.\n\n## Quickstart\n\nAfter install + restarting your MCP client, just paste a Medium URL into the chat:\n\n\u003e Read https://medium.com/zrealm-ios-dev/a-milestone-of-1-000-followers-on-medium-6fd11e9704f2 and summarize the three biggest takeaways.\n\nThe LLM picks `read_medium_post` automatically and answers from the full article — not a Cloudflare error, not a 1.5 KB preview.\n\nOther prompts that auto-route to this server:\n\n- _\"Translate this Medium post to English: \\\u003curl\\\u003e\"_\n- _\"Save this article as Markdown to `~/Documents/articles`: \\\u003curl\\\u003e\"_ — uses `download_medium_post`\n- _\"Is my Medium reader setup working?\"_ — uses `validate_setup`\n\nIf 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.\n\n## Integration with MCP Clients\n\n`mcp-medium-reader install` adds the server registration to the supported clients automatically. Restart each client afterwards.\n\n| Client          | Config File                                                                 |\n| --------------- | --------------------------------------------------------------------------- |\n| 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) |\n| Claude Code     | `~/.claude.json`                                                            |\n| OpenAI Codex    | `~/.codex/config.toml`                                                      |\n| Gemini CLI      | `~/.gemini/settings.json`                                                   |\n\nLimit which clients to install for:\n\n```bash\nmcp-medium-reader install --clients=claude-desktop,gemini\n```\n\nAvailable IDs: `claude-desktop`, `claude-code`, `codex`, `gemini`.\n\nThe registered server name is `medium-reader` (so it shows up in the LLM tool-call UI as `medium-reader.read_medium_post`, etc.).\n\n### Manual Configuration\n\nIf you'd rather edit config files yourself, the entry is:\n\n```json\n\"medium-reader\": {\n  \"command\": \"mcp-medium-reader\"\n}\n```\n\nFor Codex (`config.toml`):\n\n```toml\n[mcp_servers.medium-reader]\ncommand = \"mcp-medium-reader\"\n```\n\n## Available Tools\n\n### `read_medium_post`\n\nFetch a single Medium post by URL → Markdown in chat.\n\n**Parameters:**\n\n- `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).\n\nIf the post is paywalled, the response prepends a step-by-step recovery script the LLM can follow.\n\n### `download_medium_post`\n\nDownload a single Medium post (Markdown + images) to disk.\n\n**Parameters:**\n\n- `url`: Medium post URL.\n- `output_dir` (optional): Directory to write into. Files land under `\u003coutput_dir\u003e/Output/zmediumtomarkdown/`. Accepts `~/...`. Defaults to the current working directory of the MCP server process.\n\nRe-runs are skip-if-unchanged.\n\n### `validate_setup`\n\nInspect the dependency state and (optionally) run a live test request.\n\n**Parameters:**\n\n- `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.\n\n## Configuration\n\n### Environment Variables\n\n| Variable                                        | Description                                                                  | Required |\n| ----------------------------------------------- | ---------------------------------------------------------------------------- | -------- |\n| `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 |\n| `ZMEDIUM_COOKIE_CACHE_PATH`                     | Override the gem cookie cache path probed by `doctor` (defaults to `~/.zmediumtomarkdown`). | No |\n\nThere are **no credentials env vars** — Medium cookies belong to the gem, not the MCP server.\n\n### What's Stored Where\n\n| Data                                                         | Location                                                | Owner                       |\n| ------------------------------------------------------------ | ------------------------------------------------------- | --------------------------- |\n| Medium login cookies (`sid`, `uid`, `cf_clearance`, `_cfuvid`) | `~/.zmediumtomarkdown` (AES-256-GCM, mode 0600)         | ZMediumToMarkdown gem       |\n| Ruby runtime pin (`rubyPath`, `GEM_HOME`, `GEM_PATH`)        | `\u003cconfig_dir\u003e/runtime.json` (mode 0600 on POSIX)        | mcp-medium-reader           |\n\n## Subcommands\n\n```\nmcp-medium-reader [serve]         Start MCP stdio server (default).\nmcp-medium-reader init            Guided setup: dep check + Ruby runtime bind + MCP client install.\nmcp-medium-reader install [...]   Add server entry to MCP client configs only.\n                                  Optional --clients=\u003cid,id,...\u003e.\n                                  IDs: claude-desktop, claude-code, codex, gemini.\nmcp-medium-reader doctor          Print platform / dependency / runtime / cookie-cache status.\nmcp-medium-reader help            Show help.\nmcp-medium-reader version         Show version.\n```\n\n## Troubleshooting\n\n### Gem not found / wrong Ruby selected\n\n```\nZMediumToMarkdown gem not found\nor: Configured Ruby runtime is invalid\n```\n\nInstall the gem and bind a stable Ruby runtime:\n\n```bash\ngem install ZMediumToMarkdown      # or: gem update ZMediumToMarkdown\nmcp-medium-reader init             # rebinds runtime.json from the active shell\n```\n\nRun `init` from a shell where both `ruby` and the gem work, so the bound `rubyPath` / `GEM_HOME` are valid.\n\n### Gem version too old\n\n```\nZMediumToMarkdown X.Y.Z is too old\n```\n\n```bash\ngem update ZMediumToMarkdown\n```\n\nmcp-medium-reader requires gem version 3.5.2 or later.\n\n### Paywalled post returns only a public preview\n\nThe gem's cookie cache is missing or stale. Run the gem standalone once in your own Terminal:\n\n```bash\nZMediumToMarkdown --auth\n```\n\nChrome 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.\n\n### Cloudflare blocked the request\n\nmcp-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:\n\n1. **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.\n2. **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:\n\n   ```bash\n   ZMediumToMarkdown -p \u003cpost-url\u003e\n   ```\n\n   Worker proxy / cookie setup guide: \u003chttps://github.com/ZhgChgLi/ZMediumToMarkdown/blob/main/wiki/Setting-Up-Medium-Cookies-and-a-Cloudflare-Worker-Proxy.md\u003e\n\n## Development\n\n### Prerequisites\n\n- Node.js 18+\n- Ruby 3.2+\n- `ZMediumToMarkdown` gem 3.5.2+\n\n### Setup\n\n```bash\n# Clone the repository\ngit clone https://github.com/ZhgChgLi/mcp-medium-reader.git\ncd mcp-medium-reader\n\n# Install dependencies\nnpm install\n\n# Build the project\nnpm run build       # tsc → dist/\n\n# Run tests\nnpm test            # vitest\n\n# Run via tsx without building\nnpm run dev -- doctor\n```\n\n## License\n\nThis project is licensed under the MIT License — see the [LICENSE](./LICENSE) file for details.\n\n## Links\n\n- [GitHub Repository](https://github.com/ZhgChgLi/mcp-medium-reader)\n- [ZMediumToMarkdown gem](https://github.com/ZhgChgLi/ZMediumToMarkdown) — the Ruby gem that does the heavy lifting\n- [Model Context Protocol](https://modelcontextprotocol.io/)\n\n---\n\n## Disclaimer\n\nAll 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.\n\nFetching, 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.\n\nBy 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](https://policy.medium.com/medium-terms-of-service-9db0094a1e0f).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzhgchgli%2Fmcp-medium-reader","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fzhgchgli%2Fmcp-medium-reader","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzhgchgli%2Fmcp-medium-reader/lists"}