reddit-radar-mcp
Find Reddit threads where your product genuinely fits, reconstruct the conversation, and gate every drafted reply against a claim boundary you define.
README
reddit-radar-mcp
Find Reddit threads where your product genuinely fits, reconstruct the conversation, and gate every drafted reply against a claim boundary you define.
Read-only by design. There is no code path that posts, votes, or acts as an account, and a test asserts there never will be. Drafts are for a human to review, edit, and post.
npx reddit-radar-mcp # run as an MCP server
npm install reddit-radar-mcp # or use the scoring/gate functions directly
Requires Node 20.10+. No build step, no native dependencies.
Why this exists
The usual "social listening" tool finds mentions. That is the easy half. The hard half is everything after: is this thread actually relevant, what is the person really asking, and is the reply you are about to post true?
This package is built around three claims that came out of running it in production:
- Keyword matching produces mostly garbage. A recency + question-form + "any recommendations" heuristic scores 50/100 on literally any recent Reddit post. The fix is the anchor rule (below), and it is the most important thing here.
- Where a thread lives changes what you should say. The same question in a buyer subreddit and an engineering subreddit warrant different comments, so subreddit tiers set behaviour, not just ranking.
- A model writing promotional copy is the worst possible judge of whether it overclaimed. So the claim gate is deterministic, rule-based, and runs server-side. It refuses to hand back a blocked draft.
Quick start
Write a config:
Name it .mjs unless your project already sets "type": "module" — otherwise Node parses
it as CommonJS and the import fails.
// radar.config.mjs
import { defineConfig, packs, composePacks } from 'reddit-radar-mcp';
export default defineConfig({
product: {
name: 'Acme',
what: 'CI/CD pipeline observability.',
claims: ['flaky test detection', 'build timing breakdowns'],
},
queries: ['flaky tests', 'CI pipeline slow', 'build times'],
// REQUIRED. Without it, every recent question looks like an opportunity.
domainTerms: ['ci', 'pipeline', 'flaky', 'github actions', 'test suite'],
// Words that mean something else outside your niche.
ambiguousTerms: ['build', 'runner'],
tiers: {
tier1: { mode: 'PROMOTE', weight: 20, subreddits: ['devops'] },
tier2: { mode: 'PROMOTE_SOFT', weight: 15, subreddits: ['sre', 'kubernetes'] },
tier3: { mode: 'CONTRIBUTE', weight: 8, subreddits: ['ExperiencedDevs'] },
tier4: { mode: 'TECHNICAL_ONLY', weight: 3, subreddits: ['programming'] },
},
gate: {
...composePacks(packs.noPricing, packs.noFabricatedMetrics, packs.noCustomerNames),
productPattern: /\bAcme\b/i,
unsupported: [
{ term: /\bJenkins\b/i, why: 'No Jenkins integration exists.' },
],
},
});
Register it as an MCP server:
claude mcp add radar --scope user \
-e RADAR_CONFIG=/abs/path/radar.config.mjs \
-- npx reddit-radar-mcp
Then just talk to your agent: "run a sweep and show me what's worth replying to."
The anchor rule
The single most useful idea in this package.
A post is anchored only if something ties it to your domain: real domain vocabulary, an unambiguous query match, or a configured subreddit. Signals that describe the shape of a post — it is recent, it is a question, it says "recommendations" — can never carry a post on their own.
Without this gate, those shape signals sum to 40+ and pass anything. With it, a post in r/podcasts asking about a "POD episode" stops outranking a genuine buying question.
Two related behaviours fall out of the same idea:
- Ambiguous terms ("build", "POD", "detention") only count when a second domain signal is present — or when the post is in one of your subreddits, since the subreddit is itself domain context.
- Venting is penalized hard (-35). Rants out-engage buying questions, so without this the ranking inverts and you get "opportunities" that are people complaining about their coworkers.
Engagement modes
Tiers attach a mode to every result, and the sweep output repeats it next to each thread:
| Mode | Meaning |
|---|---|
PROMOTE |
Name the product, describe the fitting capability, disclose affiliation. |
PROMOTE_SOFT |
Answer first. Mention the product only if they are asking for tooling. |
CONTRIBUTE |
Share insight. Product only as context for who you are. |
TECHNICAL_ONLY |
Do not pitch. Nobody there is buying; promo gets removed. |
The draft gate
check_draft runs two independent checks and refuses to return a blocked draft.
Claim gate (factCheck) — deterministic rules over your claim boundary. Starter packs
cover the four common failure modes:
| Pack | Blocks |
|---|---|
noPricing |
Dollar figures, per-unit rates, price-tier comparisons |
noFabricatedMetrics |
Invented percentages, uptime/SLA claims, unverifiable scale |
noCustomerNames |
Client references (even anonymous), case studies with measured outcomes |
noMarketingSpeak |
"leverage", "seamless", "robust", "game-changing" (WARN) |
requireDisclosure |
Naming your product without disclosing affiliation |
Two behaviours worth knowing:
- Denials are always allowed. "We do not support Jenkins" passes. An early version blocked it, which pushed drafts toward silence about gaps — the opposite of the intent. Conceding a real limitation is the cheapest credibility available.
- Capability checks are assertion-scoped. "Jenkins is a solid choice if you need self-hosting" does not trip the gate, because it is not a claim about your product.
Quality gate (styleCheck) — catches text that reads as unedited generated filler:
em dashes, semicolons, curly quotes, negation framing ("not just X, it's Y"), marketing
vocabulary, flat sentence rhythm, and thin substance.
This is not AI-detection evasion. It cannot be and does not try to be. Many subreddits ban low-effort content, and moderators read comments rather than running classifiers. So the gate enforces what that rule actually asks for: real substance, no filler. The human still edits and posts, and disclosure is always present.
Pass your domain vocabulary so the substance check knows what a specific noun looks like:
styleCheck(draft, { anchorTerms: [...config.domainTerms, ...config.featureTerms] });
MCP tools
| Tool | Does | LLM cost |
|---|---|---|
plan_sweep |
Returns search URLs + the page extractor to run on each | none |
ingest_sweep |
Dedupes, scores, ranks swept results into an opportunity list | none |
score_thread |
0–100 score for one post with per-point reasoning | none |
analyze_thread |
Reconstructs a thread + returns binding claim constraints | none |
parse_thread_html |
Same, from client-side browser extraction | none |
check_draft |
The enforcement point. APPROVED or BLOCKED | none |
get_claim_boundary |
What may and may not be claimed | none |
Every tool is deterministic. The model supplies the writing; the server supplies the facts and the veto.
Reddit access
Three interchangeable adapters behind one interface:
BrowserRedditClient— reads the same public pages a person reads, from your own browser tool. No credentials. This is the default path today.RedditApiClient— OAuth against the official Data API. Access is approval-gated; see docs/REDDIT-ACCESS.md.FixtureRedditClient— local JSON fixtures for tests and development.
Fixtures run through the same normalizers as live responses, so parsers are genuinely exercised rather than first meeting real data in production.
A caveat worth stating plainly: browser mode depends on Reddit's DOM, and Reddit ships redesigns. The extractors are written to fail loudly rather than silently return empty threads that look like "no discussion found."
Programmatic use
import { scoreRelevance, factCheck, styleCheck, packs, composePacks } from 'reddit-radar-mcp';
import config from './radar.config.js';
const result = scoreRelevance(post, config, { matchedQueries: ['flaky tests'] });
if (result.passed) console.log(result.score, result.reasons);
const gate = factCheck(draft, config.gate);
if (!gate.allowed) console.log(gate.findings);
Ethics and policy
This tool exists to help you find conversations you can genuinely contribute to. It will not help you astroturf.
- No posting automation. Not implemented, and enforced by test.
- Disclose affiliation.
requireDisclosureis on by default. Undisclosed vendor comments get removed and can earn a permanent ban, which ends the channel entirely. - One account. Reddit's Responsible Builder Policy prohibits registering multiple accounts for the same use case. Do not use this to run a sockpuppet network.
- Threads are scored, never people. Nothing here profiles an author, in line with Reddit's prohibition on inferring user characteristics.
- Respect subreddit rules.
TECHNICAL_ONLYexists because pitching in the wrong place is both rude and counterproductive.
Environment variables
| Variable | Default | Purpose |
|---|---|---|
RADAR_CONFIG |
— | Required. Absolute path to your config (.js ESM with a default export, or .json). |
REDDIT_MODE |
browser |
browser, live, or fixture. See docs/REDDIT-ACCESS.md. |
REDDIT_CLIENT_ID |
— | live mode only. |
REDDIT_CLIENT_SECRET |
— | live mode only. |
REDDIT_USER_AGENT |
— | live mode only. Must be <platform>:<appid>:<version> (by /u/<user>). |
REDDIT_QPM |
60 |
Rate limit for live mode. Lower than Reddit's advertised 100 on purpose. |
RADAR_LOG_LEVEL |
info |
silent, error, warn, info, debug. |
RADAR_LOG_FORMAT |
json |
json or text. |
Full annotated list in .env.example.
Logs go to stderr only. On stdio transport stdout carries the JSON-RPC protocol, so anything written there corrupts the stream. Credentials in URLs and sensitive keys are redacted before logging.
Troubleshooting
Everything scores as an opportunity. Your domainTerms are too generic or missing.
That list is what anchors a post to your domain, and without it the shape signals carry
posts on their own. Config validation treats an empty list as an error for this reason.
Nothing scores at all. Check that domainTerms uses words that actually appear in post
titles. Terms of 5+ characters match simple inflections (pipeline → pipelines); shorter
ones match exactly, so app will not match apps.
A good draft is blocked as thin substance. Pass your vocabulary as anchorTerms — the
MCP server does this from your config automatically, but a direct styleCheck() call needs
it explicitly.
An honest limitation is blocked. It should not be; denials are explicitly allowed. Please report it.
Reddit shows "Prove your humanity". A cold search can hit a JS challenge. Loading any subreddit page first usually clears it for the session.
"Cannot use import statement outside a module". Your config is a .js file in a
project without "type": "module", so Node parses it as CommonJS. Either name it
radar.config.mjs or add "type": "module" to the nearest package.json. A .json
config avoids the question entirely, at the cost of regex literals and composePacks.
More in SUPPORT.md.
Tests
npm test # 33 unit tests
npm run smoke # 14 checks over the real MCP wire protocol
npm run verify # everything, including the metadata consistency guard
The safety suite asserts that no client exposes a write method, no source file references a Reddit write endpoint, and the package exports no posting function.
Contributing
Issues and PRs welcome — see CONTRIBUTING.md. Note the permanent exclusions listed there: posting automation, multi-account support, and AI-detection evasion are deliberate non-goals rather than missing features.
Support this project
If this saves you time, sponsoring on GitHub helps keep it maintained. Entirely optional — the package is MIT and always will be.
Non-financial contributions are just as useful: a bug report with a reproducing config, a rule pack that generalizes, or a note about a scoring case that surprised you.
License
MIT — see LICENSE.
Recommended Servers
playwright-mcp
A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.
Audiense Insights MCP Server
Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.
Magic Component Platform (MCP)
An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.
VeyraX MCP
Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.
graphlit-mcp-server
The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.
Kagi MCP Server
An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.
Neon Database
MCP server for interacting with Neon Management API and databases
Exa Search
A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.
Qdrant Server
This repository is an example of how to create a MCP server for Qdrant, a vector search engine.
E2B
Using MCP to run code via e2b.