# Slander MCP - Product Requirements Document
**Status:** Draft v0.2
**Last Updated:** 2026-01-22
## Overview
An MCP server that finds humorous roasts, jokes, and memes about any character (real or fictional) by searching Twitter/X. Uses social proof (engagement metrics) to rank content, with targeted LLM involvement for query generation, batch quality assessment, and nickname extraction.
## Problem Statement
When users want to find popular roasts, nicknames, or memes about a character, manually searching Twitter is tedious and requires domain knowledge of what to search for. This MCP automates the discovery of "greatest hits" slander by:
1. Intelligently generating search queries
2. Fetching and filtering content
3. Ranking by crowd-sourced engagement
4. Extracting nicknames and slang
**Primary use case:** Entertainment. Users want to laugh.
## Core User Flow
1. User provides a character name (e.g., "LeBron James", "Darth Vader")
2. MCP generates effective search queries via LLM
3. MCP fetches posts, looping until quality threshold met
4. MCP ranks posts by engagement, extracts nicknames
5. Returns top text posts, top media posts, and discovered nicknames
---
## Tools
### 1. `generate_search_query`
**Purpose:** Generate effective Twitter search queries for finding slander about a target.
**Input:**
```typescript
{
target: string; // Name of character to search for
}
```
**Output:**
```typescript
{
queries: string[]; // List of search queries to use
}
```
**Implementation:** LLM-generated. The MCP calls an LLM internally to produce creative, context-aware queries. This is critical because:
- Generic templates miss character-specific slander (e.g., "LeChoke" for LeBron)
- Fictional characters need context-aware queries (e.g., "Darth Vader breathing" jokes)
- The query quality determines result quality
**Example:**
```
Input: { target: "LeBron James" }
Output: { queries: ["LeBron James ratio", "LeBron hairline", "LeChoke", "LeBron excuses", ...] }
```
---
### 2. `fetch_posts`
**Purpose:** Fetch posts from Twitter for a given query, looping until quality threshold is met.
**Input:**
```typescript
{
query: string; // Single search query
loop_limit?: number; // Max fetch iterations (default: 5)
count?: number; // Posts per fetch (default: 10)
}
```
**Output:**
```typescript
{
posts: Array<{
id: string;
text: string;
author: string;
author_handle: string;
url: string;
created_at: string;
likes: number;
retweets: number;
replies: number;
has_media: boolean;
media_urls?: string[];
}>;
iterations: number; // How many fetch loops occurred
stopped_reason: string; // "quality_threshold" | "loop_limit"
}
```
**Implementation:**
1. Fetch batch of posts from Twitter (with `filter:safe` for NSFW filtering)
2. Accumulate results
3. Call LLM: "Is this batch good enough for roasting [target]?"
4. If yes → return results
5. If no → fetch next page (unless `loop_limit` reached)
**Loop termination:**
- **Quality threshold met:** LLM determines batch has sufficient quality roasts
- **Loop limit reached:** Stop and return what we have
This balances quality (don't return garbage) with practicality (don't loop forever for obscure targets).
---
### 3. `rank_posts`
**Purpose:** Rank fetched posts by engagement, separate text from media, extract nicknames.
**Input:**
```typescript
{
posts: Post[]; // Output from fetch_posts (can combine multiple fetches)
top_n?: number; // Results per category (default: 3)
}
```
**Output:**
```typescript
{
text_posts: Array<{
text: string;
author: string;
author_handle: string;
url: string;
likes: number;
retweets: number;
replies: number;
score: number;
}>;
media_posts: Array<{
text: string;
author: string;
author_handle: string;
url: string;
likes: number;
retweets: number;
replies: number;
score: number;
media_urls: string[];
}>;
nicknames: string[]; // Extracted nicknames/slang for the target
}
```
**Engagement Score Formula:**
```
score = (likes * 1.0) + (retweets * 2.0) + (replies * 0.5)
```
- **Retweets weighted highest:** "I need others to see this" is a strong signal for humor
- **Replies weighted lowest:** Often indicates arguments, not quality
**Nickname Extraction:** LLM-based. After ranking, the MCP asks an LLM: "What nicknames or slang terms for [target] appear in these posts?" This catches both obvious nicknames ("LeChoke") and subtle ones ("the king of excuses").
**Media Ranking:** Same engagement logic as text posts. If this proves insufficient (images need to be "seen" to judge), we can add LLM image evaluation later.
---
## Technical Design
### LLM Involvement Summary
| Step | LLM Used? | Purpose |
|------|-----------|---------|
| Query generation | Yes | Produce creative, context-aware queries |
| Per-post evaluation | No | Avoided to prevent muting + reduce cost |
| Batch quality check | Yes | Determine when to stop fetching |
| Nickname extraction | Yes | Extract nicknames from ranked posts |
| Media evaluation | No (v1) | Same engagement logic as text |
**Rationale:** We use LLM strategically at decision points (query, batch quality, nicknames) but avoid per-post evaluation to prevent AI "muting" of edgy content and reduce costs.
### NSFW Filtering
Baked into `fetch_posts` via Twitter's `filter:safe` search operator. No opt-out for v1.
### Twitter API Access
X developer portal is now developer-friendly. Use official API.
---
## Decisions Made
| Decision | Choice | Rationale |
|----------|--------|-----------|
| Single tool vs multiple | Multiple (3 tools) | Composability - LLM can iterate on queries, aggregate fetches |
| Query generation | LLM-generated | Templates miss context-specific slander |
| Post evaluation | Social proof only | Avoid muting, reduce cost |
| Batch quality check | LLM-evaluated | Know when to stop fetching |
| Nickname extraction | LLM-based | Catches subtle nicknames |
| Media ranking | Same as text (v1) | Start simple, add AI vision later if needed |
| Target scope | Any character | Real or fictional |
| NSFW filtering | Yes, baked in | Use Twitter's filter:safe |
---
## Open Questions
1. **Caching:** Should we cache results? For how long?
2. **Rate Limiting:** How do we handle API limits gracefully?
3. **LLM Model:** Which model for internal LLM calls? (Cost vs quality tradeoff)
---
## Future Considerations (v2+)
- **Configuration options:** Tone (harsh vs playful), time range, explicit content tolerance
- **Multi-platform:** Extend to Reddit, Instagram comments
- **Nickname database:** Persist discovered nicknames across requests
- **Trending slander:** Surface figures currently being roasted
- **AI image evaluation:** Have LLM assess meme quality if engagement ranking insufficient
---
## Success Metrics
- Returns relevant, actually-funny content >80% of the time
- Latency <10s for typical request (accounts for LLM calls)
- Discovers at least 1 nickname for well-known figures
- Stops fetching early (quality threshold) >50% of the time