32992834ee
- Xiaohongshu search via local MCP service (opt-in, zero impact if service not running) - Reddit public JSON fallback (works with zero API keys) - Reddit priority: ScrapeCreators -> OpenAI -> public fallback - Updated env.py: Reddit always available via public fallback Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
353 lines
17 KiB
Markdown
353 lines
17 KiB
Markdown
---
|
|
title: "feat: Free Reddit via MCP — Remove OpenAI Key Requirement"
|
|
type: feat
|
|
date: 2026-02-06
|
|
---
|
|
|
|
# feat: Free Reddit via MCP — Remove OpenAI Key Requirement
|
|
|
|
## Overview
|
|
|
|
Replace the OpenAI Responses API (paid) for Reddit searching with a **free, zero-config Reddit MCP server**, eliminating the need for an `OPENAI_API_KEY` to get Reddit results in the last30days skill. This work happens in a **new forked repo** to keep the current release branch clean.
|
|
|
|
## Problem Statement
|
|
|
|
Today, the last30days skill requires an OpenAI API key (`OPENAI_API_KEY`) to search Reddit. This is the most expensive dependency in the stack — OpenAI charges per-call for the Responses API with web search. Users without an OpenAI key get zero Reddit results and fall back to WebSearch-only mode, which loses the engagement metrics (upvotes, comments) that make last30days uniquely valuable.
|
|
|
|
**Goal:** Make Reddit search completely free with no API key registration required.
|
|
|
|
## Research Findings
|
|
|
|
### Option Analysis
|
|
|
|
Five approaches were evaluated. Two stand out:
|
|
|
|
| Option | Free? | Search? | Engagement? | Setup | Notes |
|
|
|--------|-------|---------|-------------|-------|-------|
|
|
| **reddit-mcp-buddy** (Node.js) | Yes (anonymous mode) | Yes | Yes | One CLI command | 371 stars, 3-tier auth, most popular |
|
|
| **mcp-server-reddit** (Python) | Yes (redditwarp) | **No** | Yes | pip install | Recommended by ClaudeLog, but browse-only |
|
|
| **.json URL trick** (curl) | Yes | Yes | Yes | Zero | ~10 req/min, no dependencies |
|
|
| Reddit OAuth (PRAW) | Free but needs registration | Yes | Yes | App registration | 100 req/min, most reliable |
|
|
| RSS feeds | Yes | Basic | **No** | Zero | Least useful, no metrics |
|
|
|
|
### Top Contender: reddit-mcp-buddy
|
|
|
|
**GitHub:** [karanb192/reddit-mcp-buddy](https://github.com/karanb192/reddit-mcp-buddy)
|
|
- 371 stars, actively maintained (last update: Jan 29, 2026)
|
|
- **Anonymous mode** — zero credentials, ~10 req/min
|
|
- **MCP tools:** `search_reddit`, `browse_subreddit`, `get_post_details`, `user_analysis`, `reddit_explain`
|
|
- **Install:** `claude mcp add --transport stdio reddit-mcp-buddy -s user -- npx -y reddit-mcp-buddy`
|
|
- Returns full engagement metrics (score, num_comments, upvote_ratio)
|
|
- TypeScript/Node.js (npx, no Python dependency)
|
|
|
|
### Strong Alternative: .json URL trick
|
|
|
|
- Append `.json` to any Reddit URL → full JSON response
|
|
- Search: `https://www.reddit.com/search.json?q=TOPIC&sort=relevance&t=month&limit=100`
|
|
- Zero dependencies, zero auth, zero setup
|
|
- Returns same data as official API (score, num_comments, created_utc, upvote_ratio)
|
|
- ~10 req/min rate limit (sufficient for skill use)
|
|
- Can be called via `curl` or Python `urllib`
|
|
|
|
### Also Considered
|
|
|
|
- **Hawstein/mcp-server-reddit** (134 stars, Python, no auth) — **No search tool**, only browse subreddits. Cannot replace OpenAI's keyword search capability.
|
|
- **Arindam200/reddit-mcp** (262 stars, PRAW) — Has search but **requires Reddit OAuth credentials**. Not truly zero-config.
|
|
- **adhikasp/mcp-reddit** (348 stars) — Hot threads only, no search. Last updated Dec 2024.
|
|
|
|
## Proposed Solution
|
|
|
|
### Approach A: MCP-First with .json Fallback (Recommended)
|
|
|
|
Add a new module `mcp_reddit.py` that:
|
|
|
|
1. **Detects** if a Reddit MCP server is configured in the user's Claude Code session
|
|
2. **If MCP available:** Calls MCP `search_reddit` tool via the skill's `allowed-tools` (the skill already allows tools — MCP tools become available when configured)
|
|
3. **If no MCP:** Falls back to Reddit's `.json` URL endpoints via `urllib` (stdlib, no dependencies)
|
|
4. **Normalize** MCP/JSON results to the existing `RedditItem` schema
|
|
5. **Enrich** with `reddit_enrich.py` (already works — fetches real thread data)
|
|
|
|
This gives users three tiers of Reddit access:
|
|
- **Tier 1 (best):** Reddit MCP installed → full search + engagement via MCP tools
|
|
- **Tier 2 (good):** No MCP, no keys → `.json` URL search + enrichment
|
|
- **Tier 3 (existing):** OpenAI key present → existing `openai_reddit.py` still works (backward compat)
|
|
|
|
### Approach B: .json-Only (Simpler)
|
|
|
|
Skip MCP entirely. Add a `reddit_json.py` module that uses `https://www.reddit.com/search.json` directly. Simpler but misses the MCP ecosystem integration.
|
|
|
|
### Approach C: MCP-Only (Cleaner)
|
|
|
|
Require MCP setup. Simpler code but adds a user setup step (`claude mcp add ...`).
|
|
|
|
**Recommendation: Approach A** — MCP-first with .json fallback gives zero-config Reddit search for everyone while rewarding users who set up MCP with a better experience.
|
|
|
|
## Technical Approach
|
|
|
|
### Architecture
|
|
|
|
```
|
|
User invokes /last30days "topic"
|
|
│
|
|
├─ env.py detects sources:
|
|
│ ├─ MCP reddit available? → mcp_reddit.py
|
|
│ ├─ No MCP, no key? → reddit_json.py (.json URL trick)
|
|
│ └─ OPENAI_API_KEY set? → openai_reddit.py (existing, backward compat)
|
|
│
|
|
├─ X search (Bird CLI / xAI — unchanged)
|
|
│
|
|
└─ Normalize → Score → Dedupe → Render (unchanged)
|
|
```
|
|
|
|
### New Files
|
|
|
|
#### `scripts/lib/reddit_json.py`
|
|
- Uses `urllib.request` (stdlib) to call Reddit's `.json` search endpoint
|
|
- URL: `https://www.reddit.com/search.json?q={topic}&sort=relevance&t=month&limit=100`
|
|
- Custom `User-Agent` header (required by Reddit)
|
|
- Parses response into the same format as `openai_reddit.py` output
|
|
- Handles pagination via `after` token if depth=deep (multiple requests)
|
|
- Rate limiting: 1 second delay between requests
|
|
|
|
#### `scripts/lib/mcp_reddit.py`
|
|
- Detects MCP availability (check if Reddit MCP tools exist in session)
|
|
- Formats MCP tool calls for `search_reddit` with topic + date filters
|
|
- Normalizes MCP response to match existing `RedditItem` schema
|
|
- Falls back to `reddit_json.py` if MCP unavailable
|
|
|
|
#### Modified Files
|
|
|
|
- **`scripts/lib/env.py`** — Add Reddit source detection: MCP → .json → OpenAI
|
|
- **`scripts/last30days.py`** — Route Reddit search through new priority chain
|
|
- **`scripts/lib/normalize.py`** — Add normalizer for `.json` endpoint response format
|
|
- **`SKILL.md`** — Document MCP setup as optional enhancement
|
|
|
|
### MCP Integration Design
|
|
|
|
The MCP approach has a subtle challenge: the last30days skill runs as a **Python subprocess** (`python3 last30days.py`), but MCP tools are available to **Claude's session**, not to subprocesses.
|
|
|
|
**Two paths to solve this:**
|
|
|
|
**Path 1: SKILL.md orchestration** — The SKILL.md workflow calls the MCP `search_reddit` tool directly (before or in parallel with the Python script), saves the MCP response to a temp file, and the Python script reads it:
|
|
|
|
```
|
|
SKILL.md workflow:
|
|
1. Call MCP search_reddit → save to /tmp/last30days_mcp_reddit.json
|
|
2. Call python3 last30days.py --reddit-from=/tmp/last30days_mcp_reddit.json --emit=compact
|
|
3. Python script reads pre-fetched Reddit data instead of calling OpenAI
|
|
```
|
|
|
|
**Path 2: .json only for subprocess** — The Python script uses `.json` URLs directly (no MCP needed in subprocess). MCP is a bonus for users who want Claude to also browse specific threads interactively.
|
|
|
|
**Recommendation: Path 2 for MVP, Path 1 as enhancement.** The `.json` approach is self-contained, testable, and doesn't require SKILL.md workflow changes. MCP can be layered on later.
|
|
|
|
### Source Priority Chain (updated env.py)
|
|
|
|
```python
|
|
def get_reddit_source(config: dict) -> str:
|
|
"""Returns: 'mcp', 'json', 'openai', or 'none'"""
|
|
# 1. Check for pre-fetched MCP data (from SKILL.md)
|
|
if os.path.exists(MCP_REDDIT_CACHE_PATH):
|
|
return 'mcp'
|
|
# 2. Always available — no key needed
|
|
# (reddit .json endpoints are free)
|
|
return 'json'
|
|
# 3. OpenAI key present → legacy path
|
|
# if config.get('OPENAI_API_KEY'):
|
|
# return 'openai'
|
|
```
|
|
|
|
For MVP, the `.json` path is **always available** so it becomes the default. OpenAI path remains as opt-in for users who want higher rate limits.
|
|
|
|
### Data Mapping: .json → RedditItem
|
|
|
|
Reddit `.json` search returns `data.children[].data` with:
|
|
|
|
```json
|
|
{
|
|
"title": "Post title",
|
|
"permalink": "/r/subreddit/comments/abc123/...",
|
|
"subreddit": "ClaudeAI",
|
|
"score": 42,
|
|
"num_comments": 15,
|
|
"upvote_ratio": 0.95,
|
|
"created_utc": 1738800000,
|
|
"selftext": "Post body...",
|
|
"url": "https://...",
|
|
"author": "username"
|
|
}
|
|
```
|
|
|
|
Maps cleanly to existing `RedditItem`:
|
|
|
|
| .json field | RedditItem field | Notes |
|
|
|-------------|------------------|-------|
|
|
| `title` | `title` | Direct |
|
|
| `permalink` | `url` | Prepend `https://www.reddit.com` |
|
|
| `subreddit` | `subreddit` | Direct |
|
|
| `score` | `engagement.score` | Direct |
|
|
| `num_comments` | `engagement.num_comments` | Direct |
|
|
| `upvote_ratio` | `engagement.upvote_ratio` | Direct |
|
|
| `created_utc` | `date` | Convert epoch → YYYY-MM-DD |
|
|
| `selftext` | (used for relevance) | AI relevance scoring needed |
|
|
| `author` | (not in current schema) | Ignore for now |
|
|
|
|
### Relevance Scoring Without AI
|
|
|
|
The current `openai_reddit.py` gets relevance scores **from OpenAI** (the AI judges how relevant each result is). With `.json` endpoints, we lose that AI relevance judgment.
|
|
|
|
**Options:**
|
|
1. **Keyword matching** — Score based on how many query terms appear in title + selftext. Simple but effective.
|
|
2. **TF-IDF-like** — Weight rarer query terms higher. More accurate but more code.
|
|
3. **Let Claude judge** — Pass results to Claude in SKILL.md and have Claude score relevance. Most accurate but changes the workflow.
|
|
4. **Skip relevance, rely on Reddit's sort** — Reddit's `sort=relevance` already ranks by relevance. Trust it and use position-based scoring (first result = 1.0, last = 0.5).
|
|
|
|
**Recommendation: Option 4 for MVP.** Reddit's search relevance ranking is already good. Use position-based relevance (1.0 → 0.5 linear decay over result set) combined with the existing engagement-based scoring. This requires zero external dependencies and no AI calls.
|
|
|
|
## Implementation Phases
|
|
|
|
### Phase 0: Fork Repository
|
|
|
|
- [ ] Create new repo `last30days-free-reddit` (or branch in private repo)
|
|
- [ ] `git clone /Users/mvanhorn/last30days-skill-private /Users/mvanhorn/last30days-free-reddit`
|
|
- [ ] Create feature branch: `feat/free-reddit`
|
|
- [ ] Verify all existing tests pass on the new branch
|
|
|
|
### Phase 1: reddit_json.py — Core .json Search
|
|
|
|
- [ ] Create `scripts/lib/reddit_json.py`
|
|
- `search_reddit_json(topic: str, depth: str, date_from: str, date_to: str) -> list[dict]`
|
|
- Build search URL with `q`, `sort=relevance`, `t=month`, `limit` (25/50/100 by depth)
|
|
- Custom `User-Agent: last30days-skill/2.0 (by /u/last30days-bot)`
|
|
- Parse `data.children[].data` → list of raw dicts
|
|
- Handle pagination for deep mode (follow `after` token, max 3 pages)
|
|
- Rate limit: `time.sleep(1.0)` between requests
|
|
- Error handling: 429 (rate limit), 403 (blocked), network errors → return empty + error msg
|
|
- [ ] Create `tests/test_reddit_json.py`
|
|
- Mock HTTP responses using fixture files
|
|
- Test: basic search parsing, pagination, rate limit handling, error cases
|
|
- [ ] Create `fixtures/reddit_json_search_sample.json`
|
|
- Real `.json` search response (sanitized)
|
|
|
|
### Phase 2: Normalize + Score .json Results
|
|
|
|
- [ ] Add `normalize_reddit_json()` to `scripts/lib/normalize.py`
|
|
- Convert `.json` response format → `RedditItem` schema
|
|
- `created_utc` (epoch) → `YYYY-MM-DD` string
|
|
- `permalink` → full URL
|
|
- Position-based relevance: `1.0 - (index / total * 0.5)` → range [1.0, 0.5]
|
|
- Date confidence: `"high"` (Reddit provides exact timestamps)
|
|
- [ ] Update `scripts/lib/score.py` if needed
|
|
- `.json` results already have real engagement metrics — existing scoring formula works as-is
|
|
- No penalty needed (unlike WebSearch which lacks engagement)
|
|
- [ ] Add tests for normalization + scoring of `.json` data
|
|
|
|
### Phase 3: Integration into Orchestrator
|
|
|
|
- [ ] Update `scripts/lib/env.py`
|
|
- Add `get_reddit_source(config) -> str` returning `'json'`, `'openai'`, or `'none'`
|
|
- Priority: `.json` always available (default), `'openai'` if key present and user prefers
|
|
- Add `REDDIT_SOURCE` config option: `auto` (default), `json`, `openai`
|
|
- Update `get_available_sources()` to always include Reddit (since `.json` is free)
|
|
- Update `get_missing_keys()` — Reddit no longer shows as "missing"
|
|
- [ ] Update `scripts/last30days.py`
|
|
- Add `_search_reddit_json()` function alongside existing `_search_reddit()`
|
|
- Route based on `get_reddit_source()`: json → `_search_reddit_json()`, openai → `_search_reddit()`
|
|
- Skip `reddit_enrich.py` for `.json` results (already have real engagement metrics!)
|
|
- Update progress/stats output to show source: "Reddit (free)" vs "Reddit (OpenAI)"
|
|
- [ ] Update SKILL.md promo messaging
|
|
- Remove "Add OPENAI_API_KEY for Reddit" messaging
|
|
- Instead: "Reddit search included free! Add OpenAI key for AI-enhanced relevance scoring."
|
|
- [ ] Integration tests: full pipeline with `.json` mock data
|
|
|
|
### Phase 4: Testing & Polish
|
|
|
|
- [ ] Run all existing tests — ensure backward compatibility
|
|
- [ ] Manual test: invoke `/last30days` with NO API keys → should get Reddit + WebSearch results
|
|
- [ ] Manual test: invoke with OPENAI_API_KEY → should still use OpenAI path (backward compat)
|
|
- [ ] Manual test: compare result quality — `.json` vs OpenAI for same topic
|
|
- [ ] Update README.md — document free Reddit access
|
|
- [ ] Update SPEC.md — document new source priority chain
|
|
|
|
### Phase 5 (Future): MCP Enhancement Layer
|
|
|
|
- [ ] Detect Reddit MCP in Claude's session
|
|
- [ ] SKILL.md pre-fetches via MCP `search_reddit` → saves to temp file
|
|
- [ ] Python script reads pre-fetched MCP data via `--reddit-from=` flag
|
|
- [ ] MCP results get AI-judged relevance (since Claude sees them)
|
|
- [ ] Better than `.json` position-based relevance
|
|
|
|
## Acceptance Criteria
|
|
|
|
### Functional
|
|
- [ ] `/last30days "any topic"` returns Reddit results with **zero API keys configured**
|
|
- [ ] Reddit results include real engagement metrics (score, comments, upvote_ratio)
|
|
- [ ] Results are properly scored, deduped, and rendered (same quality as OpenAI path)
|
|
- [ ] Existing OpenAI path still works when key is present
|
|
- [ ] Existing X search (Bird CLI / xAI) is unchanged
|
|
- [ ] Stats box shows correct source indicator ("Reddit (free)" or "Reddit (OpenAI)")
|
|
|
|
### Non-Functional
|
|
- [ ] No external Python packages (stdlib only — `urllib.request`, `json`, `time`)
|
|
- [ ] Respects Reddit rate limits (~10 req/min for unauthenticated)
|
|
- [ ] Graceful degradation if Reddit blocks requests (429/403 → empty results + error msg)
|
|
- [ ] All new code has unit tests with fixtures (no live API calls in tests)
|
|
|
|
### Quality Gates
|
|
- [ ] All existing tests pass (zero regressions)
|
|
- [ ] New tests cover: search parsing, normalization, scoring, error handling, pagination
|
|
- [ ] Manual smoke test passes with zero API keys
|
|
|
|
## Risk Analysis
|
|
|
|
| Risk | Likelihood | Impact | Mitigation |
|
|
|------|-----------|--------|------------|
|
|
| Reddit blocks `.json` endpoints | Low | High | Fall back to OpenAI path; monitor for 429s |
|
|
| `.json` rate limit too restrictive | Medium | Medium | 1s delay between requests; cache results |
|
|
| Relevance quality lower without AI scoring | Medium | Medium | Reddit's `sort=relevance` is decent; can add keyword scoring later |
|
|
| Reddit changes `.json` response format | Low | Medium | Schema validation in normalize; fixture-based tests catch changes |
|
|
| Node.js MCP server dependency conflicts | N/A (Phase 5) | Low | MCP is optional enhancement, not required |
|
|
|
|
## Dependencies
|
|
|
|
- **None** — this feature uses only Python stdlib and Reddit's public `.json` endpoints
|
|
- Phase 5 (future) would add optional dependency on an MCP server
|
|
|
|
## Repository Setup
|
|
|
|
```bash
|
|
# Fork into new working repo
|
|
cp -r /Users/mvanhorn/last30days-skill-private /Users/mvanhorn/last30days-free-reddit
|
|
cd /Users/mvanhorn/last30days-free-reddit
|
|
|
|
# Create feature branch
|
|
git checkout -b feat/free-reddit
|
|
|
|
# Verify existing tests
|
|
python3 -m pytest tests/ -v
|
|
```
|
|
|
|
The new repo is disposable — once the feature is validated, changes merge back into `last30days-skill-private` via PR or cherry-pick.
|
|
|
|
## References
|
|
|
|
### Internal
|
|
- `scripts/lib/openai_reddit.py` — Current Reddit search (to be replaced/supplemented)
|
|
- `scripts/lib/env.py:get_available_sources()` — Source detection logic to update
|
|
- `scripts/lib/normalize.py` — Add `.json` normalizer alongside existing
|
|
- `scripts/lib/reddit_enrich.py` — May be skippable for `.json` results (already have engagement)
|
|
- `scripts/lib/schema.py:RedditItem` — Target schema (unchanged)
|
|
|
|
### External
|
|
- [Reddit .json search endpoint](https://www.reddit.com/search.json?q=test&sort=relevance&t=month&limit=25)
|
|
- [Simon Willison — Scraping Reddit via JSON API](https://til.simonwillison.net/reddit/scraping-reddit-json)
|
|
- [karanb192/reddit-mcp-buddy](https://github.com/karanb192/reddit-mcp-buddy) — Best MCP option for Phase 5
|
|
- [Hawstein/mcp-server-reddit](https://github.com/Hawstein/mcp-server-reddit) — ClaudeLog-recommended MCP (no search though)
|
|
- [Reddit API Rate Limits Guide](https://painonsocial.com/blog/reddit-api-rate-limits-guide)
|
|
|
|
### Research Sources
|
|
- last30days skill output — community recommendations for Reddit search tools
|
|
- GitHub search — 10+ Reddit MCP repos evaluated
|
|
- npm/PyPI registries — package availability confirmed
|
|
- ClaudeLog — [Reddit MCP reference](https://claudelog.com/claude-code-mcps/reddit-mcp/)
|