reddit-ads-mcp
# reddit-ads-mcp
A small, self-hosted [MCP](https://modelcontextprotocol.io) server for the **Reddit Ads API v3**, in Python.
- **Read** accounts, campaigns, ad groups, ads, and performance reports.
- **Write** (opt-in) — pause/activate, set budgets, patch fields, create entities.
- **No third party.** Auth is a direct OAuth2 refresh-token exchange with reddit.com using *your* app credentials. Nothing is proxied anywhere.
- **Safe by default.** Mutations are refused unless `REDDIT_ADS_WRITE_ENABLED=true`.
> Inspired by the read-only C# [RedditAdsMcp](https://github.com/mkerchenski/RedditAdsMcp); rewritten in Python with opt-in write support and a self-hosted token helper.
**Status:** reads + the core small-writes (status / budget / field patch) are **verified
against the live Reddit Ads API v3** (2026-06-30). `create_*` are wired but not yet
validated end-to-end — marked experimental below.
## Tools
| Tool | Access | Description |
|------|--------|-------------|
| `list_accounts` | read ✓ | All ad accounts reachable with your credentials |
| `list_campaigns` | read ✓ | Campaigns for an account |
| `list_ad_groups` | read ✓ | Ad groups, optionally by `campaign_id` |
| `list_ads` | read ✓ | Ads, optionally by `ad_group_id` |
| `get_performance_report` | read ✓ | Custom date range / fields / breakdowns |
| `get_daily_performance` | read ✓ | Last N days by DATE + CAMPAIGN_ID |
| `set_campaign_status` / `set_ad_group_status` | write ✓ | Pause / activate / archive |
| `set_campaign_budget` / `set_ad_group_budget` | write ✓ | Daily budget in dollars (auto micro-conversion) |
| `update_campaign` / `update_ad_group` | write ✓ | Patch arbitrary fields |
| `create_campaign` / `create_ad_group` | write ⚠️ | Create entities (experimental — unvalidated) |
Write tools are only registered when `REDDIT_ADS_WRITE_ENABLED=true`.
### API notes (learned from the live v3 API)
- **Collections are nested, single entities are top-level:** list/create use
`ad_accounts/{id}/campaigns`, but get/update use `campaigns/{id}` (no account prefix).
- **Budgets are micro-currency:** `goal_value: 6000000` = $6.00, `goal_type: "DAILY_SPEND"`.
The `set_*_budget` tools take plain dollars and convert for you.
- **Reports are a read-only `POST`** to `ad_accounts/{id}/reports` with a `{data:{…}}` body.
- **Write OAuth scope is `adsedit`** (not `adswrite`).
## Setup
### 1. Create a Reddit Ads API app
[ads.reddit.com](https://ads.reddit.com) → **Developer Applications** → **Create a new app**.
Set the **redirect URI** to any HTTPS URL **you control** — it only needs to show the `code`
query param Reddit appends after you click Allow. Copy the **App ID** and **Secret**.
### 2. Get a refresh token (one-time)
Open this URL (URL-encode your redirect; drop `%20adsedit` for read-only):
```
https://www.reddit.com/api/v1/authorize?client_id=YOUR_APP_ID&response_type=code&state=mcp&redirect_uri=YOUR_REDIRECT&duration=permanent&scope=adsread%20adsedit
```
Click **Allow**, copy the `code` from the redirected URL, then:
```bash
python scripts/get_refresh_token.py \
--client-id YOUR_APP_ID --client-secret YOUR_SECRET \
--code THE_CODE --redirect-uri YOUR_REDIRECT
```
It prints your permanent `REDDIT_ADS_REFRESH_TOKEN`.
### 3. Find your account ID
[ads.reddit.com](https://ads.reddit.com) → account dropdown — it looks like `a2_xxxxxxxx`.
### 4. Configure your MCP client
With [uv](https://docs.astral.sh/uv/) (no global install needed):
```json
{
"mcpServers": {
"reddit-ads": {
"command": "uv",
"args": ["run", "--directory", "/path/to/reddit-ads-mcp", "python", "-m", "reddit_ads_mcp"],
"env": {
"REDDIT_ADS_CLIENT_ID": "your_app_id",
"REDDIT_ADS_CLIENT_SECRET": "your_secret",
"REDDIT_ADS_REFRESH_TOKEN": "your_refresh_token",
"REDDIT_ADS_ACCOUNT_ID": "a2_xxxxxxxx",
"REDDIT_ADS_WRITE_ENABLED": "false"
}
}
}
}
```
Flip `REDDIT_ADS_WRITE_ENABLED` to `"true"` only when you want mutations (and only if the
refresh token was granted the `adsedit` scope).
## Development
```bash
uv sync
uv run python -m reddit_ads_mcp # starts the stdio server (needs env vars)
```
## License
MIT © Sorin Mihailescu
TDQS
Scored across 6 tools
The list tools are clearly distinct by entity, but the two performance tools (get_daily_performance vs get_performance_report) overlap in purpose. Although one is for recent days and the other for custom date ranges, an agent might misselect them without careful description reading.
All tool names follow a consistent verb_noun pattern using snake_case (get_*, list_*). The naming is predictable and clear.
With 6 tools, the set is well-scoped for an ad management server. It covers essential entities (accounts, campaigns, ad groups, ads) and reporting, without unnecessary redundancy.
The tool surface is read-only: only listing and reporting. Missing write operations (create, update, delete) for campaigns, ads, etc., which are critical for actual ad management. Agents cannot perform any operational tasks.