Campaigns and ad groups are created paused, the whole-tree builder builds paused, and the A/B variants arrive paused too — going live means calling the state tools afterwards, on purpose. Budget carries a second guard on top of that: a change is checked against a configured ceiling and needs an explicit confirmation. The rest of the limits are stated rather than discovered at runtime — name lengths, minimum budgets, page sizes, bounded start times and a conversion batch cap all appear in the tool descriptions.
A full client for the OpenAI Ads API — campaigns, ad groups, ads, creatives, audiences, geo targeting, conversions and reporting — shaped so that nothing it creates can spend money until a separate call activates it. It runs locally under either a Python or a Node runtime, and a hosted read-only endpoint exists for discovery.
- get_account, which verifies the key and returns the ad account's id, name, timezone, currency and settings — the first call to make.
- The whole tree with matching list, get, create and update tools: campaigns carrying a lifetime budget, ad groups with a billing event of impression or click and a maximum bid, and ads carrying a creative.
- build_campaign, which creates one campaign, one ad group and several ads in a single guarded workflow with everything paused, and returns whatever was created plus the error when a step fails.
- Activation as its own decision: set_campaign_state, set_ad_group_state and set_ad_state each activate, pause or archive, and each states that activation can start real spend or delivery once the rest of the tree is active.
- Creatives through upload_creative, which takes exactly one of a URL or a local file and returns a file id, and create_ad, which builds a chat card from a target URL and that file, or a product ad template.
- bulk_ab_test_hints, which creates several paused variants under one ad group, and draft_context_hints, which composes targeting hints deterministically from an audience, intent, keywords and product with no external model call.
- get_insights, the reporting tool, returning impressions, clicks, spend, CTR, CPC, CPM and conversions at account, campaign, ad group or ad scope, with segments, granularity and a time range.
- Audiences and targeting: list_audiences and get_audience, manage_audience to create one from members, upload one from a file or archive it, and search_geo, which returns the geo and DMA ids to pass into a campaign's targeting.
- Conversion setup in one tool — manage_conversions creates pixels and API keys, reads and sets event settings with an attribution window, and reads conversion insights — plus send_conversions, which posts events to the ingest host, validates the batch cap and the timestamp window, and never logs the user data in them.
An OpenAI Ads API key in OPENAI_ADS_API_KEY, and a runtime — the same server ships for uvx and for npx, so take whichever your machine already has. Two guards are worth setting before an agent touches this: OPENAI_ADS_MCP_READONLY registers the read tools only, and OPENAI_ADS_BUDGET_CEILING_USD caps what a budget change is allowed to become. A hosted read-only endpoint exists for discovery, and it never holds anyone's key: a caller must send their own on each Ads call.
One command plus a key — uvx openai-ads-mcp, then supply credentials
