# Changelog of Reddit MCP Server — Claude, ChatGPT, Cursor, Codex (`makework36/reddit-mcp-server`) Actor

- **URL**: https://apify.com/makework36/reddit-mcp-server/changelog.md
- **Full Actor documentation**: https://apify.com/makework36/reddit-mcp-server.md

## Changelog

### 1.0.12 — 2026-08-08

Fixes every tool returning `Bootstrap could not obtain session cookies`.

- Replaced `undici` with `impit` as the HTTP client. Reddit now fingerprints the
  caller: from the same residential IP, curl gets `200` and undici gets `403` on
  every attempt, so the cookie bootstrap could never succeed. `impit`
  impersonates a real Chrome signature and the bootstrap works again.
- A `200` response carrying the "Welcome to Reddit" HTML interstitial is now
  treated as a dead session — the cookies are dropped and the session rotated,
  instead of spending the remaining retries on a session Reddit already refused.
- Redirects are no longer followed: a `302` to `/login` means the session was
  refused, and is now retried on a fresh sticky IP.

### 1.0.0 — 2026-04-21

Initial public release.

- Apify Standby MCP server exposing seven Reddit tools over Streamable HTTP (`/mcp`).
- JSON-RPC 2.0, protocol version `2025-06-18`.
- Tools: `search_reddit`, `get_subreddit_posts`, `get_post_with_comments`, `get_user_posts`, `get_user_comments`, `get_subreddit_info`, `get_trending_subreddits`.
- Residential proxy (Apify Proxy `RESIDENTIAL` group) with fallback to default Apify Proxy.
- Puppeteer-extra + stealth against `old.reddit.com` JSON endpoints; images/fonts/CSS/media blocked via request interception.
- Pay-per-event pricing: `$0.02` per successful tool call. `initialize`, `tools/list`, `ping` and notifications are free.
- `GET /` returns a server manifest listing the tools (useful for debugging MCP client integrations).

### 2.0.0 — 2026-09-01

**Rebuilt on Reddit's public Atom feeds.** Same 7 tools, same names, same inputs.

Reddit closed anonymous access to `*.json` on every host and restricted new Data API
registrations to moderation use cases. The impit + sticky-residential + cookie bootstrap
that worked in August now fails on every attempt: there is no challenge to pass.

- `lib/reddit-fetch.js` rewritten: fetches `/.rss` feeds, no cookies, no bootstrap, no
  sticky sessions. 429 is treated as a pace limit with exponential backoff, not a block.
- **Residential proxy demoted to fallback.** The feeds answer 200 from datacenter IPs;
  residential was ~80% of the cost and bought nothing.
- New `lib/atom.js`: minimal Atom parser shared by the tools.
- `search_reddit` now passes `type=link`. Without it Reddit's search feed also returns
  communities (`t5_`), which arrived as posts with no author and the subreddit name in
  the title field.
- Vote data (`score`, `upvoteRatio`, `numComments`) and moderation flags are `null`, never
  `0`, so a caller can tell "unknown" from "zero". Every tool description says so, since
  an MCP client picks tools by reading them.
- `get_subreddit_info` now serves what the feed header carries (name, title, description,
  icon, URL, last activity); subscriber and active-user counts are `null` — Reddit does
  not publish them any more.
- `get_post_with_comments` requires `subreddit` (the comment feed is per-subreddit) and
  reports `commentSortHonoured: false`, since the feed serves one fixed order.

### 2.1.0 — 2026-09-02

**Opt-in HTML route that fills in the engagement fields.** The Atom feeds carry no
vote data, so `score`, `upvoteRatio` and `numComments` had been `null` since 2.0.0.
This release recovers them from Reddit's rendered pages, without giving up the feeds
as the base route.

- New `lib/session.js`, `lib/htmlRoute.js`, `lib/shreddit.js` and `lib/richData.js`.
  The session pool minted by `reddit-session-refresher` is read from its Key-Value
  Store **by ID** (`REDDIT_SESSION_STORE_ID`): a run under `LIMITED_PERMISSIONS`
  cannot resolve a named store it did not create.
- Reddit's budget is **225 requests per 10 minutes and it is counted PER SESSION,
  not per IP** (both halves measured 2026-09-02). More proxies do not raise the
  ceiling; more sessions do. The route paces itself, reads the remaining budget off
  every response, brakes before a session runs out and rotates to the next one.
- 🔑 **A 200 proves nothing on this route.** Three response shapes were measured and
  are told apart by size plus the presence of the element we came for: ~8.4 KB with
  no `<shreddit-post>` is the JavaScript challenge, ~190 KB is the IP block page,
  and only 500 KB–1.2 MB *with* the marker is a real page. The middle one looks
  substantial and would sail past a naive size check.
- Promoted items (`<shreddit-ad-post>`, `is-ad`, and friends) are never parsed as
  organic posts or comments.
- `selftext` and comment bodies no longer carry Reddit's feed trailer
  ("`&#32;` submitted by /u/author \[link] \[comments]"). New `lib/atomText.js` cuts it
  off and decodes numeric entities, instead of merely stripping tags.
- Own proxy read from `REDDIT_PROXY_URL`, so it never lands in the repo. A
  `proxyConfiguration` set on the Actor still takes precedence.
- `get_subreddit_info` gains `weeklyActiveUsers`. It is deliberately **not** called
  `subscribers`: it reads 292,024 for r/Drizzy but 3,599,935 for r/explainlikeimfive,
  which has well over 20 million subscribers.
- Everything here is best-effort. If the session pool is unavailable the tools keep
  working on the feeds alone and say so in the log, with the concrete reason.

### 2.1.3 — 2026-09-02

Two bugs found by calling all seven tools for real against 2.1.2, plus two smaller
fixes. The first two are also shipped on their own as 2.0.3, because they are in the
build customers are running.

- 🔴 **A 429 no longer kills the whole tool call.** On the feed route a rate limit
  threw a bare error that `runTool` turned into `isError: true`; three calls died
  that way in a single validation pass. Now `Retry-After` is honoured when Reddit
  sends one, the fallback ladder carries ±30 % jitter so parallel containers stop
  retrying in lockstep, and rate-limit retries get their **own** budget instead of
  eating the attempts reserved for transport errors. Only once all of that is spent
  does it throw, and what it throws says it is a pace limit worth retrying.
- 🔴 **Partial results are kept instead of discarded.** If a rate limit lands part
  way through a paginated feed, the pages already fetched are returned with
  `partial: true` and a `notice` telling the caller to retry for the rest. Only a
  failure on the *first* page still raises, because then there is nothing to hand
  back.
- 🔴 **`get_user_comments` returned `created: null` on every comment.**
  `/user/<name>/comments/.rss` entries carry `<updated>` and **no** `<published>`,
  and the transform read only `published`. It now falls back to `updated` for both
  comments and posts. `edited` deliberately still reads the raw pair, so a missing
  `published` cannot be misreported as an edit.
- `get_subreddit_info.url` returned the feed's alternate link with our own paging
  parameter glued on (`.../r/Drizzy/?limit=1`) as if it were the subreddit's
  canonical URL. The query and fragment are now stripped.
- Test suite grown to 60 checks, including a live one that stands up a local server
  answering a real 429 over a real socket.
