Write-capable MCP server for the OpenAI Ads platform. Provides 27 tools for ad account management, campaign/ad group/ad CRUD, image upload, insights, and local helpers (validate_chat_card, parse_brief).
Strong tool naming (verb_noun pattern throughout) and comprehensive parameter schemas with types and descriptions. All 27 tools have clear, actionable descriptions (avg ~150 chars). Output schemas are documented in descriptions. Key strengths: pagination support, irreversible operation safeguards (confirm_archive pattern), read-modify-write logic for nested updates. Weaknesses: output field documentation is implicit rather than explicit (no formal response schema definitions); some parameter descriptions could be more prescriptive about constraints (e.g., title length 3-50 is stated but not in schema); error handling guidance is minimal.
Activate a paused ad.
Activate a paused ad group.
Activate a paused campaign so it starts delivering (spend begins).
Archive an ad PERMANENTLY. Irreversible — requires confirm_archive=true after explicit user confirmation.
Archive an ad group PERMANENTLY. Irreversible — requires confirm_archive=true after explicit user confirmation.
Archive a campaign PERMANENTLY. Irreversible — requires confirm_archive=true after explicit user confirmation.
Output schemas not formally documented. Tool descriptions state what is returned (e.g., 'including its status, budget, and targeting') but no explicit JSON Schema response definitions exist. LLMs cannot reliably parse or chain results without formal output schemas.
Parameter constraints (e.g., title 3-50 chars, body <=100 chars, limit 1-500) are stated in descriptions but not enforced in JSON Schema via minLength/maxLength/enum. LLMs may pass invalid values; validation happens server-side only.
Error handling lacks recovery guidance. _call() wrapper returns {error, status, body} but does not categorize errors as retryable, user-fixable, or fatal. LLMs cannot determine next steps (retry? ask user? abort?).
| Scored | Grade | Overall | Spec posture | Rubric |
|---|---|---|---|---|
| 2026-09-23 | D | 59 | 2026-07-28+ | v2 |
Create an ad (PAUSED by default) under an ad group. The only creative type is chat_card: title (3-50 chars), body (max 100 chars), and file_id (from upload_image). file_id is required.
Create an ad group (PAUSED by default) under a campaign. Billing is per IMPRESSION only (no CPC/CPA): max_bid_micros is the bid per impression, so a $60 CPM = $0.06/impression = 60,000. context_hints are free-form strings describing when the ads are useful — the only audience signal besides campaign geo.
Create a campaign (PAUSED by default). Budget is LIFETIME-only in micros (1,000,000 = 1.00 in account currency, minimum 1,000,000 — there is no daily budget). location_ids come from search_locations; omit to target all locations. Times are unix timestamps; omit start_time to begin delivering on activation.
Fetch one ad by id, including its creative (chat_card) and review_status.
Get the current ad account (id, name, currency, timezone). Call first to confirm access and learn the account currency every money field is denominated in.
Fetch one ad group by id, including its bidding_config and context_hints.
Fetch one campaign by id, including its status, budget, and targeting.
Fetch performance insights (impressions, clicks, spend, CTR, CPC, CPM) at account, campaign, ad group, or ad level. Supports time granularity (none/daily), date ranges, and field selection.
List ad groups (paginated: limit 1-500, after/before cursors, order asc|desc).
List ads (paginated: limit 1-500, after/before cursors, order asc|desc).
List campaigns (paginated: limit 1-500, after/before cursors, order asc|desc).
Deterministically extract budget micros, dates, URLs, CPM bids, and title/location candidates from a natural-language campaign brief. Local only — calls no API.
Pause an active ad (reversible).
Pause an active ad group (reversible).
Pause an active campaign (reversible — activate again any time).
Search targetable locations (country / region / DMA) by name and return their ids for campaign targeting. Always resolve ids here — never guess them.
Update an ad. Accepts partial updates; a partial creative is automatically completed from current state because the API requires nested objects sent whole.
Update an ad group. Accepts partial updates; a partial bidding_config is automatically completed from current state because the API requires nested objects sent whole.
Update a campaign. Accepts partial updates ({"name": ...}, {"budget": {...}}, {"targeting": {...}}, {"status": ...}); nested objects are automatically completed from the campaign's current state because the API requires them sent whole.
Upload an image (URL or file) and return a file_id for use in ad creatives. Provide exactly one of image_url (JSON) or file_path (multipart).
Validate a chat_card creative (title, body, file_id) without calling the API. Returns validation errors or empty list if valid.
Pagination cursors (after/before) are documented as strings but their format and semantics are not explained. LLMs cannot construct valid pagination chains without understanding cursor structure.
insights tool has vague parameter semantics. 'aggregation_level' and 'time_granularity' are both present; their relationship and valid combinations are undocumented. 'fields' and 'sort' are arrays but their element structure is not specified.