MCP server for Reddit Ads API v3 over stdio.
This repo is intended to be handed to Claude Code, Codex, or other MCP-capable agents so they can inspect, report on, and operate Reddit Ads accounts programmatically.
94Reddit Ads API operations from the checked-in OpenAPI spec4auth helper tools98MCP tools total- automatic bearer token handling
- refresh-token support
- optional pagination following for paginated endpoints
Tool names follow this pattern:
reddit_ads_list_campaignsreddit_ads_create_campaignreddit_ads_get_a_reportreddit_ads_post_conversion_eventsreddit_ads_generate_bid_suggestion
Auth helper tools:
reddit_ads_auth_statusreddit_ads_auth_get_authorization_urlreddit_ads_auth_exchange_codereddit_ads_auth_refresh_access_token
All endpoints from the current OpenAPI spec are registered in MCP.
Live verification was run against a real authorized Reddit Ads account on March 11, 2026. Most of the surface is working. Two endpoints are implemented but currently return upstream Reddit 500 errors with valid requests:
List Keyword SuggestionsGet Catalog Import Report
Install these first:
- Node.js
20+ - npm
- a Reddit developer app with Ads API access
- a user-authorized Reddit OAuth refresh token for the advertiser account you want to operate
Check versions:
node -v
npm -vgit clone <your-repo-url>
cd reddit-ads-mcp
npm installCopy the example env file:
cp .env.example .envSet at least:
REDDIT_APP_ID=...
REDDIT_APP_SECRET=...
REDDIT_REDIRECT_URI=...
REDDIT_REFRESH_TOKEN=...
REDDIT_SCOPES=adsread,adsedit,adsconversions,history,read
REDDIT_USER_AGENT=mcp:reddit-ads-mcp:v0.1.0 (by /u/your_reddit_username)If you do not have a refresh token yet, follow the OAuth flow below first.
npm run buildnpm test
npm run test:discoveryIf you have a real refresh token configured, continue with:
npm run test:authorized
npm run test:full-writes
npm run test:remainingnpm startThis starts the MCP server over stdio using:
node dist/src/index.jsExample stdio MCP config:
{
"mcpServers": {
"reddit-ads": {
"command": "node",
"args": ["/absolute/path/to/reddit-ads-mcp/dist/src/index.js"],
"cwd": "/absolute/path/to/reddit-ads-mcp",
"env": {
"REDDIT_APP_ID": "your_app_id",
"REDDIT_APP_SECRET": "your_app_secret",
"REDDIT_REDIRECT_URI": "https://your-redirect.example/",
"REDDIT_REFRESH_TOKEN": "your_refresh_token",
"REDDIT_SCOPES": "adsread,adsedit,adsconversions,history,read",
"REDDIT_USER_AGENT": "mcp:reddit-ads-mcp:v0.1.0 (by /u/your_reddit_username)"
}
}
}
}If your MCP client supports inheriting env from the shell, you can keep the secrets in .env and only set command, args, and cwd.
- Install dependencies.
- Configure environment variables.
- Build the server.
- Run it over stdio for your MCP client.
npm install
npm run build
npm startUse .env.example as the template.
Recommended auth mode:
REDDIT_APP_IDREDDIT_APP_SECRETREDDIT_REDIRECT_URIREDDIT_REFRESH_TOKENREDDIT_SCOPESREDDIT_USER_AGENT
Important:
- For real Ads account access, use a user-authorized OAuth refresh token.
- App ID + secret alone are not enough for normal
/me, business, campaign, reporting, and account CRUD access. client_credentialsis only useful for limited fallback reads and is off by default.
Use the auth helper tools or do the exchange yourself.
Typical flow:
- Call
reddit_ads_auth_get_authorization_url - Open the returned URL in a browser while logged into the correct Reddit Ads user
- Approve access
- Capture the returned
code - Exchange it with
reddit_ads_auth_exchange_code - Save the
refresh_tokenintoREDDIT_REFRESH_TOKEN
Once REDDIT_REFRESH_TOKEN is present, the MCP server can refresh access tokens automatically.
Live verification was performed against a real authorized Reddit Ads account, but no account-specific IDs are documented here.
Agents should always discover resources dynamically instead of hardcoding them.
Recommended discovery order:
reddit_ads_auth_statusreddit_ads_list_my_businessesreddit_ads_get_businessreddit_ads_list_ad_accounts_by_businessreddit_ads_get_ad_accountreddit_ads_list_profiles_by_businessreddit_ads_list_pixels_by_businessreddit_ads_list_product_catalogs
Verified working live through MCP:
- business and account discovery
- campaign create/get/update/archive
- ad group create/get/update/archive
- ad create/get/update/archive
- standard post creation for ad creative
- structured post creation job, polling, retrieval, and update
- saved audience create/get/update
- custom audience create, add users, remove users, delete
- reporting
- ad account history
- conversions API posting
- pixels, profiles, funding instruments reads
- lead gen form create/list/get
- product catalog create/get/update/delete
- product create/list/delete
- product set create/list/get/update/list-products/delete
- product feed create/list/get/update/delete
- catalog import listing
- catalog import issues listing
- geolocations list + validation
- communities list + search + suggestions
- keyword validation
- bid suggestion
- channel planning reach
Implemented but currently returning upstream Reddit 500 on live retest:
reddit_ads_list_keyword_suggestionsreddit_ads_get_catalog_import_report
Some endpoints required payload details that were not obvious from a superficial reading of the spec.
This minimal payload worked:
{
"data": {
"name": "example-campaign",
"configured_status": "PAUSED",
"objective": "IMPRESSIONS"
}
}This required delivery configuration:
{
"data": {
"campaign_id": "campaign_id_here",
"name": "example-adgroup",
"configured_status": "PAUSED",
"bid_type": "CPM",
"bid_strategy": "BIDLESS",
"goal_type": "DAILY_SPEND",
"goal_value": 5000000,
"start_time": "2026-03-11T05:00:00Z",
"end_time": "2026-03-18T05:00:00Z",
"targeting": {
"communities": ["StableDiffusion"],
"custom_audience_ids": [],
"devices": [],
"excluded_communities": [],
"excluded_custom_audience_ids": [],
"excluded_geolocations": [],
"excluded_keywords": [],
"expand_targeting": false,
"geolocations": ["US"],
"interests": [],
"keywords": [],
"carriers": [],
"suppression_event_types": [],
"age_targeting": {},
"languages": null,
"gender": null
}
}
}This worked:
{
"data": {
"ad_group_id": "ad_group_id_here",
"configured_status": "PAUSED",
"name": "example-ad",
"post_id": "post_id_here"
}
}Use the body wrapper:
{
"data": {
"starts_at": "2025-07-28T00:00:00Z",
"ends_at": "2026-03-10T23:00:00Z",
"fields": ["IMPRESSIONS", "CLICKS", "SPEND"],
"breakdowns": ["DATE"]
}
}A valid request needed bid_strategy and delivery config:
{
"data": {
"bid_type": "CPM",
"bid_strategy": "BIDLESS",
"goal_type": "DAILY_SPEND",
"goal_value": 5000000,
"currency": "USD",
"duration": {
"start_time": "2026-03-11T05:00:00Z",
"end_time": "2026-03-18T05:00:00Z"
},
"targeting": {
"communities": ["StableDiffusion"],
"custom_audience_ids": [],
"devices": [],
"excluded_communities": [],
"excluded_custom_audience_ids": [],
"excluded_geolocations": [],
"excluded_keywords": [],
"expand_targeting": false,
"geolocations": ["US"],
"interests": [],
"keywords": [],
"carriers": [],
"locations": ["FEED"],
"platforms": ["DESKTOP"],
"suppression_event_types": [],
"view_modes": ["CARD"],
"age_targeting": {},
"languages": null,
"gender": null
}
}
}The safe filter that worked in live tests was:
{
"data": {
"name": "example-product-set",
"filter": "{}"
}
}Human-readable filter strings such as brand = "ModelsLab" returned 400 invalid filter format.
Creation is asynchronous:
reddit_ads_create_structured_post_creation_job- poll
reddit_ads_get_structured_post_creation_job - wait for
status: "SUCCESS" - use returned
post_id - call
reddit_ads_get_structured_post - call
reddit_ads_update_structured_postif needed
Feed object CRUD works immediately. Import records appear only after schedule-driven import processing begins, so agents should poll reddit_ads_list_catalog_imports for a short period instead of assuming immediate availability.
These are not MCP registration problems. They were reproduced with valid authenticated requests:
reddit_ads_list_keyword_suggestions->500 Internal Server Errorreddit_ads_get_catalog_import_report->500 error retrieving presigned URL
Agents should treat these as upstream Reddit failures and degrade gracefully.
Useful scripts in this repo:
- tests/smoke.ts
- tests/discovery.ts
- tests/authorized-exercise.ts
- tests/full-write-flows.ts
- tests/remaining-coverage.ts
Run them with:
npm run build
npm test
npm run test:discovery
npm run test:authorized
npm run test:full-writes
npm run test:remainingIf you are an agent using this MCP:
- Start with discovery, not writes.
- Prefer refresh-token auth.
- Expect some endpoints to require polling.
- Handle
429and500responses explicitly. - Use
PAUSEDor non-delivering states for validation writes when possible. - Clean up temporary catalogs, feeds, campaigns, ad groups, ads, and audiences after tests.
- Treat keyword suggestions and catalog import report as known upstream failures unless Reddit behavior changes.
To refresh the spec:
npm run refresh:specSource:
https://ads-api.reddit.com/api/v3/openapi.json