ADM Google Ads MCP Server

Connect Claude Code, Codex, Cursor or Windsurf to your Google Ads accounts. Read campaign data in plain language and draft new Search campaigns with ADM AI.

Hosted MCP Server

npx add-mcp 'https://app.adm.cc/mcp'

Installs into Claude Code, Codex, Cursor and more

Documentation

The ADM Google Ads skill lets an AI coding tool read your Google Ads accounts and build Search campaigns from a written plan. It works with Claude Code, Codex, Cursor and Windsurf. The skill is one file, SKILL.md, that describes the workflow; the work itself runs through the ADM MCP server, which connects the AI tool to the Google Ads accounts linked to your ADM account.

  • Read on every plan: reports, search terms, keywords, ads and assets are available on every ADM plan, including Free.
  • Write on paid plans: creating campaigns, ad groups, ads, keywords, negative keywords and assets requires a paid plan (Starter, Professional or Enterprise).
  • Hosted ADM today: the hosted ADM server currently offers the read tools and AI drafts. The write tools are not yet available there; to create a campaign in Google Ads, use the campaign wizard in ADM.
  • Preview before every change: ADM validates each change and returns a preview. Nothing is written to Google Ads until you approve it.
  • New campaigns are paused: every campaign is created paused and is enabled only when you ask for it in a separate message.

Addresses:

  • Skill file: https://adm.cc/docs/google-ads-skill/SKILL.md (folder name adm-google-ads)
  • MCP server: https://app.adm.cc/mcp (Streamable HTTP), authenticated with an ADM API key sent as Authorization: Bearer adm_xxx
  • Contract version: 2026-10-03

1. Get started in three steps

Step 1. Create an API key. Sign in to ADM and open https://app.adm.cc/apikeys. Choose the access level:

  • Read only: reports and configuration only. Works on every plan, including Free.
  • Read & write: reports plus the write tools. Write tools require an active paid plan.

The full key is shown once. Copy it before closing the dialog.

Step 2. Ask the AI tool to install ADM. Copy the prompt below, replace adm_xxx with your key and send it to the AI tool. The AI tool follows the steps in section 5 to connect the ADM MCP server, save the skill file and test the connection. When it has finished, restart the AI tool if it asks you to.

Install the ADM MCP server and the ADM Google Ads skill by following https://adm.cc/docs/google-ads-skill#ai-install. My ADM API key is adm_xxx

The key remains in the conversation history. If that history may be seen by others, rotate the key in ADM (section 10). To configure the server by hand instead, see section 4.

Step 3. Give instructions in plain language. For example: "Which of my campaigns spent the most in the last 30 days?" or "Create the campaigns in plan.md." Section 3 has more examples.

2. Which ADM tools the skill calls

What you ask forADM tools
Choose or switch the Google Ads accountget_accounts
Reports and questions about performanceget_campaigns, get_ad_groups, get_keywords, get_search_terms, get_ads, get_negative_keywords, find_negative_conflicts, get_paused_summary, get_assets, get_conversion_actions, get_customizers
See performance by device (mobile, computer, tablet)get_device_performance
Lower or exclude bids on mobile, computer or tabletset_device_bid_adjustments
See which locations spent and convertedget_location_performance, set_location_bid_adjustments
Confirm a preview with only its idconfirm_preview
Build campaigns from a plancreate_search_campaign, add_ad_groups, add_keywords, add_ads
Negative keywordsadd_account_negative_keywords, create_shared_negative_list, attach_shared_negative_list
Sitelinks, callouts, structured snippets, prices, promotions and callsadd_sitelinks, add_callouts, add_structured_snippets, add_prices, add_promotions, add_calls
Business nameadd_business_name
Image assets and business logosUpload endpoint POST https://app.adm.cc/mcp/uploads, then add_images or add_business_logo
Ad groups drafted by ADM AIdraft_ad_groups, get_ad_group_draft
A new campaign drafted by ADM AIdraft_campaign, get_campaign_draft, then create_search_campaign
Check a build and resume after an interruptionget_campaign_setup, get_operation
Enable or pause a campaignset_campaign_status
Pause a campaign, or change its name, daily budget, bidding or languagesupdate_campaign
Add or remove targeted or excluded locationsupdate_campaign_locations
Create, attach or change a shared portfolio bidding strategyget_bidding_strategies, create_bidding_strategy, attach_bidding_strategy, update_bidding_strategy
Pause or unlink an assetset_asset_link_status
Rename, pause, enable or rebid an ad groupupdate_ad_group, set_ad_group_status
Edit the text of an existing adupdate_ads
Enable, pause or remove ads or keywordsset_ad_status, set_keyword_status
Add or remove negative keywordsadd_campaign_negative_keywords, remove_negative_keywords, add_shared_negative_keywords, remove_shared_negative_keywords
Tag clicks with a custom parameter, tracking template or final URL suffixset_url_options

Section 6 describes every tool and its parameters.

3. What you can say after setup

  • "Which of my campaigns spent the most in the last 30 days, and how many conversions did each get?"
  • "List search terms from the last 14 days that cost money without a conversion."
  • "Create the campaigns in plan.md. Preview first and wait for my approval."
  • "Draft ad groups for the photo-enhancer section of plan.md."
  • "Create a new Search campaign from https://www.example.com/photo-enhancer. Explore the keywords, then preview it and wait for my approval."
  • "Add banner.jpg as an image asset to the photo-enhancer@DE campaign."
  • "Switch to the MySecond ad account."
  • "Tag each campaign with custom parameter myname so I can see which campaign a click came from."

Default account

When several Google Ads accounts are connected, the skill asks once which one to use and saves the choice in .adm/account.json in the project directory (customer ID and account name only, never the API key). Later requests in the same project use that account, and each operation names the account it runs on. When only one account is connected, the skill uses it without asking. To change the account, say "Switch to the MySecond ad account". If the saved account is later disconnected in ADM, the skill stops before the next operation, reports that the saved account is no longer connected, and asks which account to use, even when only one remains.

Example: build a campaign from a plan

This example uses a fictional brand, PixelUp, and builds one campaign from a plan entry named photo-enhancer@DE: an online photo enhancement tool advertised in Germany in German.

The plan entry:

  • Campaign photo-enhancer@DE, location Germany, one ad group photo-enhancer-DE
  • Landing page https://www.example.com/de/photo-enhancer, display path foto / verbessern
  • 8 phrase-match keywords, 6 headlines, 2 descriptions, 4 campaign negative keywords
  • General settings: Google Search only, Maximize clicks with a CPC cap, no broad match

The plan does not state a daily budget or the CPC cap, so the AI tool asks for both before building anything. The example uses 10.00 per day and a 0.40 cap in the account currency.

What to ask:

Create the photo-enhancer@DE campaign from plan.md.
Daily budget 10.00, CPC cap 0.40. Preview first and wait for my approval.

The AI tool first calls create_search_campaign without confirm. ADM validates the request and returns a preview with every error, the display width of each text, the account currency and a monthly budget estimate (daily budget × 30.4). The preview includes a preview_id. After you approve, the AI tool sends the same arguments again with "confirm": true and that preview_id.

{
  "customer_id": "123-456-7890",
  "campaign": {
    "name": "photo-enhancer@DE",
    "daily_budget": 10.00,
    "bidding": { "type": "MAXIMIZE_CLICKS", "max_cpc": 0.40 },
    "network_settings": { "google_search": true, "search_partners": false, "display_network": false },
    "geo_targets": [ { "country_code": "DE" } ],
    "negative_keywords": [ "\"kamera\"", "\"handy\"", "\"bildschirm\"", "\"monitor\"" ],
    "ad_groups": [
      {
        "name": "photo-enhancer-DE",
        "language": "de",
        "keywords": [
          "\"foto verbessern\"", "\"bildqualität verbessern\"", "\"foto schärfen\"", "\"bild vergrößern\"",
          "\"foto qualität verbessern online\"", "\"unscharfes foto scharf machen\"",
          "\"bild hochskalieren\"", "\"foto auflösung erhöhen\""
        ],
        "ads": [
          {
            "final_url": "https://www.example.com/de/photo-enhancer",
            "path1": "foto",
            "path2": "verbessern",
            "headlines": [
              { "text": "Fotoqualität verbessern" },
              { "text": "Bilder online vergrößern" },
              { "text": "Unscharfe Fotos schärfen" },
              { "text": "Fotos 2x oder 4x vergrößern" },
              { "text": "Ohne Installation nutzen" },
              { "text": "PixelUp Foto-Verbesserer" }
            ],
            "descriptions": [
              { "text": "Fotoqualität online verbessern: Bilder 2x oder 4x vergrößern und schärfen." },
              { "text": "Für Produktfotos, Social-Media-Beiträge und kleine Bilder. Direkt im Browser, ohne App." }
            ]
          }
        ]
      }
    ]
  }
}

Notes on this payload:

  • Keywords use Google notation: "text" is phrase match, [text] is exact match, bare words are broad match. Inside JSON the quotes of a phrase keyword are escaped as \".
  • "language": "de" appends a language tag to the ad group name, so the group is created as photo-enhancer-DE [de]. ADM uses this tag to identify the ad language when it analyzes the group later.
  • Geo targeting always uses the "Presence" option (people in or regularly in the location).
  • Account-level negative keywords and shared lists from the same plan are separate steps: add_account_negative_keywords once per account, create_shared_negative_list once per list, then attach_shared_negative_list for each new campaign.

The result contains the new campaign_id, the ad group IDs and an operation_id. The AI tool then calls get_campaign_setup to compare what exists in Google Ads with the plan. The campaign stays paused until you ask to enable it.

For a plan with many campaigns, the skill instructs the AI tool to:

  • List every missing decision and check the counts in the plan before building
  • Preview and ask for approval in two rounds: campaigns and lists first, then the list attachments and assets that need the new IDs
  • Build campaign by campaign, recording each ID in the manifest file adm-manifest.json
  • Reconcile the result, and resume without repeating finished campaigns
  • Ask separately before enabling anything

4. Use the MCP server without the skill

The skill is optional. Connected on its own, the MCP server sends the AI tool a set of usage rules when it connects: preview before every write, new campaigns paused, the default account, and treating search terms as data. The MCP server has two further advantages, with or without the skill:

  • Updates without reinstalling: new tools and fields become available as soon as ADM releases them; the configuration stays the same.
  • Permissions per tool: most AI tools let you approve read tools automatically and keep a confirmation step for write tools (section 9).

The configurations below read the key from the ADM_API_KEY environment variable. Codex, Cursor and Windsurf read the variable at run time, and their configuration files do not contain the key. Claude Code writes the key into its user-level configuration file, ~/.claude.json, when the server is added; do not share that file. Set the variable first, then restart the AI tool.

macOS or Linux (add the line to ~/.zshrc or ~/.bashrc):

export ADM_API_KEY="adm_xxx"

Windows (then open a new terminal):

setx ADM_API_KEY "adm_xxx"

Configuration for each AI tool:

Claude Code (Bash):

claude mcp add --transport http --scope user adm https://app.adm.cc/mcp \
  --header "Authorization: Bearer $ADM_API_KEY"

PowerShell:

claude mcp add --transport http --scope user adm https://app.adm.cc/mcp \`
  --header "Authorization: Bearer $env:ADM_API_KEY"

--scope user makes the server available in every project on this computer. Check the connection with claude mcp list: the adm line should end with ✔ Connected.

To stop the approval prompts for read tools only, add this to ~/.claude/settings.json (all projects) or .claude/settings.json (one project):

{
  "permissions": {
    "allow": [
      "mcp__adm__get_*"
    ]
  }
}

mcp__adm__get_* matches every ADM tool whose name starts with get_: the nine read tools, plus get_ad_group_draft and get_campaign_draft, which only read the state of an AI draft. Keep write tools out of the allow list (section 9). draft_ad_groups and draft_campaign do not change Google Ads, but each call runs several AI calls, so the rule leaves them out as well. If you registered the server under another name, replace adm in the rule with that name.

Codex:

codex mcp add adm --url https://app.adm.cc/mcp --bearer-token-env-var ADM_API_KEY

Codex reads ADM_API_KEY when it starts.

Cursor (~/.cursor/mcp.json for all projects, or .cursor/mcp.json in a project):

{
  "mcpServers": {
    "adm": {
      "url": "https://app.adm.cc/mcp",
      "headers": { "Authorization": "Bearer ${env:ADM_API_KEY}" }
    }
  }
}

Windsurf (~/.codeium/windsurf/mcp_config.json):

{
  "mcpServers": {
    "adm": {
      "serverUrl": "https://app.adm.cc/mcp",
      "headers": { "Authorization": "Bearer ${env:ADM_API_KEY}" }
    }
  }
}

The Codex, Cursor and Windsurf entries follow each tool's documented MCP configuration format. Restart the AI tool after changing its configuration, then ask it to list your Google Ads accounts. In Windsurf, open the file from the MCP settings with View raw config if it is not at the path above. ${env:ADM_API_KEY} is read from the environment of the AI tool's own process. Cursor and Windsurf are based on VS Code and, when opened from the Dock or an application launcher, also load variables set in ~/.zshrc or ~/.bashrc. After setting or changing the variable, quit the AI tool completely and open it again (on macOS, press Cmd+Q; closing the window does not quit the app); otherwise the header is sent without the key and the server returns HTTP 401.

Connection troubleshooting

SymptomCauseFix
HTTP 401The key is missing, mistyped, or has leading or trailing spacesCheck ADM_API_KEY, restart the AI tool, or remove the server and add it again with the key pasted exactly as shown
HTTP 401The key was revoked on the API keys pageCreate a new key and update the configuration (section 10)
HTTP 401 on WindowsThe header used a PowerShell variable inside single quotes, so the variable was not expandedUse double quotes, or paste the key directly into the command
HTTP 401 after setting the variableThe AI tool was started before the variable was setQuit the AI tool completely and open it again (on macOS, press Cmd+Q); start a terminal-based tool from a new terminal. If the error persists, start the editor from a terminal where the variable is set, for example cursor .
HTTP 404The URL is wrongThe URL must be exactly https://app.adm.cc/mcp: https, the app. subdomain, and the /mcp path
Timeout or connection refusedA corporate proxy or firewall blocks the requestSet HTTPS_PROXY before starting the AI tool, or ask your network administrator to allow app.adm.cc

A key that worked before and now returns 401 has usually been revoked. The API keys page shows when each key was last used.

5. Installation steps (for AI tools to read)

This section is written for AI tools. You only need to complete step 2 of section 1; the AI tool then follows the steps below on its own.

The user has given you an ADM API key (it starts with adm_). Install ADM as follows, then report what you did.

  1. Add the MCP server at user level, so it works in all projects. Server name: adm. URL: https://app.adm.cc/mcp (Streamable HTTP). Header: Authorization: Bearer <key>. Write the key only into your user-level MCP configuration, never into a project file or a git repository. If a server named adm already exists, replace it.
AI toolHow to add the server
Claude CodeRun claude mcp add --transport http --scope user adm https://app.adm.cc/mcp --header "Authorization: Bearer <key>"
CodexIn ~/.codex/config.toml add [mcp_servers.adm] with url = "https://app.adm.cc/mcp" and http_headers = { "Authorization" = "Bearer <key>" }
CursorIn ~/.cursor/mcp.json add "adm": { "url": "https://app.adm.cc/mcp", "headers": { "Authorization": "Bearer <key>" } } under mcpServers
WindsurfIn ~/.codeium/windsurf/mcp_config.json add "adm": { "serverUrl": "https://app.adm.cc/mcp", "headers": { "Authorization": "Bearer <key>" } } under mcpServers

Keep any other servers already in the file. On Windows, ~ is the user profile folder.

  1. Save the skill. Download https://adm.cc/docs/google-ads-skill/SKILL.md to adm-google-ads/SKILL.md inside the skills directory you read for all projects. The folder name must be adm-google-ads. Create missing directories. If you do not know that directory, use the project-level one:
AI toolSkill file path
Claude Code~/.claude/skills/adm-google-ads/SKILL.md
Codex.agents/skills/adm-google-ads/SKILL.md
Cursor.cursor/skills/adm-google-ads/SKILL.md
Windsurf.windsurf/skills/adm-google-ads/SKILL.md
  1. Test the connection. Call get_accounts on the adm server. If the tool is not available until you restart, say so.
  2. Report to the user: where the server configuration and the skill file were saved, whether a restart is needed, and the Google Ads accounts found. After a restart, the user can say "List my Google Ads accounts" to check.

Update the skill

The skill file has a version in its metadata. When it is out of date, get_accounts returns skill_update_hint and the AI tool tells you once. To update, download https://adm.cc/docs/google-ads-skill/SKILL.md, replace the local adm-google-ads/SKILL.md with it (same location as in step 2 above), and restart the AI tool.

6. Tool reference

Tools that act on an account take customer_id from get_accounts (format 123-456-7890); get_operation, get_ad_group_draft and get_campaign_draft take only their own ID. Money is always in the account currency as major units, for example 5.00, never micros. Report windows (days) are 7, 14 or 30 complete days in the account time zone, excluding today (default 7). List tools return up to limit rows (1 to 200, default 50) and a next_cursor for the next page.

Read tools

ToolWhat it returnsKey parameters
get_accountsConnected accounts (customer_id, name, currency, time zone), your plan, the key scope, whether writes are allowed, campaign creations left this month and write calls left today. With skill_version, also says whether the skill file needs an update (skill_update_hint). Call it first.skill_version
get_campaignsCampaigns with settings and performance, sorted by cost. Removed campaigns are never returned.days, status (ENABLED or PAUSED), limit, cursor
get_campaign_setupThe configuration of one campaign. include is any of ad_groups, keywords, ads, assets (default ad_groups,keywords,ads, no assets). With ad_groups, campaign assets stay on the campaign and ad group assets stay on each ad group. assets alone returns both, and ad group rows keep level and ad_group_id. Ads include ad_strength. Keyword id is the criterion id for set_keyword_status. device_bid_adjustments (only non-zero; -100 excludes that device) are on the campaign and each ad group.campaign_id, include
get_adsResponsive search ads with headlines, descriptions, pins, per-asset performance_label, ad strength and performance. text_contains matches headlines and descriptions. compact returns only ids and text. id is the ad id for update_ads and set_ad_status.campaign_id, days, text_contains, compact, limit, cursor
get_keywordsKeywords in Google notation with status, Quality Score, max CPC and performance. Filter with status, match_type and text_contains. id is the criterion id for set_keyword_status.campaign_id, days, status, match_type, text_contains, limit, cursor
get_search_termsSearch queries that triggered your ads, with the matched keyword and performance. At most the top 10,000 rows by cost (truncated is true when capped).campaign_id, days, limit, cursor
get_negative_keywordsNegative keywords by level: account, campaign, ad group and shared lists (with list IDs and attached campaigns). Each keyword id is the criterion id for remove_negative_keywords.campaign_id
get_assetsSitelink, callout, structured snippet, price, image, promotion, call, business name and business logo assets. level is customer, campaign or ad_group (omit it for all three). Also types, asset_ids, link_status, limit and cursor. asset_id is the id for set_asset_link_status.campaign_id, level, types, asset_ids, link_status, limit, cursor
get_bidding_strategiesPortfolio (shared) bidding strategies: id, name, type, target CPA or ROAS, CPC ceiling, and the campaigns on each.none
get_device_performancePerformance split by device (MOBILE, DESKTOP, TABLET), including conversion rate, cost per conversion and the current bid adjustment (0 = none, -100 = excluded). Search campaigns only. level is campaign (default) or ad_group.level, campaign_id, days, limit, cursor
get_location_performancePerformance split by where the user was. granularity is country, region (default) or city. bid_adjustment_percent is set when that location is targeted and the bid is not 0. location_id can be passed to update_campaign_locations or set_location_bid_adjustments.granularity, campaign_id, days, limit, cursor
get_ad_groupsAd groups with status, campaign, max CPC, bidding, keyword count and ad count.campaign_id, status, limit, cursor
get_paused_summaryHow many campaigns, ad groups, keywords and ads are paused, with ids and names (200 per level).none
find_negative_conflictsPositive keywords blocked by a negative at account, shared list, campaign or ad group level. Optional hypothetical negatives and keywords (up to 50) are checked before you add them.campaign_id, negatives, keywords, limit, cursor
get_conversion_actionsConversion actions: name, status, type, category, primary (counts in the Conversions column), value settings, counting and attribution. include_metrics adds the last 30 complete days in the account time zone, and the response includes date_range.include_metrics
get_customizersAd customizer attributes and their values at account, campaign and ad group level. Use {CUSTOMIZER.Name:default} in ad text; the length limit counts the default.none
get_operationThe recorded result of a write call: pending, completed, partial, failed or unknown, and the resources that were created.operation_id

Write tools

Every write tool previews first. Called without confirm, it validates and returns a preview without changing anything. Each preview returns a preview_id. After approval, call confirm_preview with that preview_id, or call the same tool again with identical arguments plus "confirm": true and that preview_id. A confirmed call without preview_id returns invalid_argument. An unconfirmed preview_id older than 30 minutes returns preview_required. A preview_id that was already confirmed returns the stored result and does not write again, including after those 30 minutes. To make the same change again, preview again and confirm with the new preview_id after approval.

ToolWhat it doesKey parameters
create_search_campaignCreates a complete Search campaign in one call: budget, bidding, networks, locations, negative keywords, shared lists, ad groups, keywords and responsive search ads. Always created paused.campaign (see the example in section 3)
add_ad_groupsAdds ad groups with keywords, negatives and ads to an existing Search campaign. Created paused unless status is ENABLED.campaign_id, ad_groups, status
add_keywordsAdds keywords, paused keywords and ad group negative keywords to one ad group. Existing keywords are reported as exists.ad_group_id, keywords, paused_keywords, negative_keywords
add_adsAdds 1 to 3 responsive search ads to an existing ad group (same ad shape as add_ad_groups, including path1 and path2). A group holds at most 3, paused ads included. An ad with the same headlines and descriptions as an existing ad is reported as exists. New ads are enabled and serve after Google review when the group and campaign are enabled.ad_group_id, ads
update_adsEdits responsive search ads in place. The ad id stays. Up to 20 ads, all or none. headlines and descriptions replace the whole list. headline_replacements and description_replacements ({from, to}) change one asset by its exact text and keep its pin; empty to deletes it. Do not combine a replacement with the whole list on the same ad.ads: {ad_id, headlines?, headline_replacements?, descriptions?, description_replacements?, final_url?, path1?, path2?}
add_account_negative_keywordsAdds negatives to the account-level list, which blocks them in every Search campaign, including running ones.keywords (up to 500)
create_shared_negative_listCreates a shared negative keyword list and optionally attaches it to campaigns in the same request.name, keywords (up to 1,000), campaign_ids
attach_shared_negative_listAttaches an existing shared list to campaigns.shared_list_id, campaign_ids
add_sitelinksAdds sitelinks: link text up to 25, two optional descriptions up to 35 each (both or neither), final URL.level, sitelinks, campaign_ids or ad_group_ids
add_calloutsAdds callouts, up to 25 each.level, callouts, campaign_ids or ad_group_ids
add_structured_snippetsAdds structured snippets. header must be an official header for language when you pass one (for example German Marken, Danish Typer). Without language, any official translation is accepted; an unknown language is warned and Google checks it on confirm. 3 to 10 values, up to 25 each.level, snippets (header, values, language), campaign_ids or ad_group_ids
add_pricesAdds price assets with 3 to 8 offerings. language_code must be one of de, en, es, es-419, fr, it, ja, nl, pl, pt-BR, pt-PT, ru, sv. Currency must be one Google supports for price assets (USD, EUR, GBP and others; not DKK or NOK).level, prices, campaign_ids or ad_group_ids
add_promotionsAdds promotion assets. promotion_target is at most 20 characters. Pass exactly one of percent_off (30 = 30% off) or money_amount_off with currency, in major units. language_code and final_url are required. Optional: occasion, discount_modifier UP_TO, one of promotion_code or orders_over_amount (also needs currency), and start_date / end_date (yyyy-MM-dd).level, promotions, campaign_ids or ad_group_ids
add_callsAdds call assets: country_code (2 letters) and phone_number. Optional call_conversion_reporting_state; with USE_RESOURCE_LEVEL_CALL_CONVERSION_ACTION, an optional call_conversion_action_id from get_conversion_actions (omitted = Google's default call conversion action).level, calls, campaign_ids or ad_group_ids
add_business_nameAdds a business name asset, at most 25 characters. Serving depends on Google's eligibility checks; the preview lists them.level (customer or campaign), business_name, campaign_ids
add_business_logoAdds a business logo from an uploaded file (see Image uploads below): square 1:1, at least 128×128. Serving depends on Google's eligibility checks; the preview lists them.level (customer or campaign), logos (upload_id, optional name), campaign_ids
add_imagesAdds image assets to Search campaigns or ad groups from uploaded files (see Image uploads below). Results are per target.level (campaign or ad_group), images (upload_id, optional name), campaign_ids or ad_group_ids
set_campaign_statusPauses or enables up to 50 campaigns. Pass campaign_id and status, or campaigns. The preview lists each daily budget and the added daily and monthly estimate for campaigns that are not already enabled. Manual CPC campaigns whose ad groups are still at a placeholder max CPC (0.05 or less) are warned.campaign_id and status, or campaigns: {campaign_id, status}
confirm_previewRuns a preview the user approved. Pass only the preview_id. The stored arguments are replayed, so the hash and the idempotency ledger are the same as confirming the original tool.preview_id
set_location_bid_adjustmentsSets a bid adjustment on a location the campaign already targets. -90 to 900, 0 clears it. Does not add or remove locations. Smart bidding ignores these adjustments.items: {campaign_id, location_id, adjustment_percent}
set_customizer_valuesCreates an ad customizer attribute if needed (TEXT, NUMBER, PRICE, PERCENT) and sets its value at account, campaign or ad group level.items: {name, type, value, level, campaign_id, ad_group_id}
update_campaignIn one atomic change: renames a campaign, pauses it, and/or sets the daily budget, bidding or languages. status accepts only PAUSED. A shared budget is rejected. Passing bidding while the campaign uses a portfolio detaches it: the campaign bids on its own, and the preview names the portfolio it leaves. bidding.max_cpc is only the Maximize Clicks ceiling; MANUAL_CPC rejects it and ad group bids are set with update_ad_group. Search campaigns cannot set languages (Google removed that in September 2026); the preview says so and changes nothing else in that call. The budget preview shows the old and new daily amount and the monthly estimates.campaign_id, at least one of name, status (PAUSED), daily_budget, bidding, languages
update_campaign_locationsAdds or removes targeted locations and excluded locations on one campaign, in one atomic change. Each location is {location_id} from get_campaign_setup or get_location_performance, or {country_code}. Removing an exclusion or adding a target widens delivery and can increase spend; the preview says so when the campaign is ENABLED. Removing the last targeted location makes the campaign target every location except remaining exclusions. Radius targets are not supported.campaign_id, at least one of add_targets, remove_targets, add_exclusions, remove_exclusions
set_device_bid_adjustmentsSets device bid adjustments on Search campaigns and ad groups (up to 50), together or not at all. device is MOBILE, DESKTOP or TABLET. -100 excludes that device. -90 to 900 lowers or raises the bid. 0 clears it (an ad group then inherits the campaign). An ad group adjustment overrides the campaign. Maximize conversions, Target ROAS and Maximize conversion value ignore every value except -100. On Target CPA the adjustment changes that device's target CPA. Setting all three devices to -100 is rejected.items: {level, campaign_id, ad_group_id, device, adjustment_percent}
set_asset_link_statusPauses, enables or unlinks assets (up to 300). REMOVED unlinks and cannot be undone; the asset itself stays. An asset linked under more than one field type at that level is changed on every link. An account-level sitelink can still show on campaigns that already have sitelinks; the preview lists every campaign it can affect. Other asset types list campaigns that do not have their own asset of that type. Results are per link.items: {level, asset_id, campaign_id, ad_group_id, status}. level is customer, campaign or ad_group. status is ENABLED, PAUSED or REMOVED
create_bidding_strategyCreates a portfolio bidding strategy (TARGET_CPA, TARGET_ROAS, MAXIMIZE_CLICKS, MAXIMIZE_CONVERSIONS, MAXIMIZE_CONVERSION_VALUE). MANUAL_CPC is rejected. max_cpc is an optional CPC ceiling. Optional campaign_ids (up to 50) attach in the same atomic change. ENABLED campaigns start a learning period.name, bidding, campaign_ids
attach_bidding_strategyAttaches campaigns to an existing portfolio (up to 50). Replaces each campaign's own bidding. A campaign on another portfolio leaves it. Results are per campaign.strategy_id, campaign_ids
update_bidding_strategyRenames a portfolio and/or changes its target CPA, target ROAS or CPC ceiling. The type cannot change. The new target applies to every campaign on the portfolio; the preview lists them.strategy_id, at least one of name, bidding
update_ad_groupRenames an ad group, sets it to PAUSED or ENABLED, and/or sets max_cpc, in one atomic change. Does not remove the ad group. max_cpc is charged only when the campaign uses MANUAL_CPC; the preview says so otherwise. An enabled ad group spends as soon as its campaign is enabled.ad_group_id, at least one of name, status (ENABLED or PAUSED), max_cpc
set_ad_statusSets ads to ENABLED, PAUSED or REMOVED (up to 100). REMOVED cannot be undone. A paused ad still counts toward the 3 responsive search ads an ad group can hold. Does not change the campaign status. Results are per ad.ads: {ad_group_id, ad_id, status}
set_keyword_statusSets positive keywords to ENABLED, PAUSED or REMOVED (up to 300). Negative keywords are refused. REMOVED cannot be undone. Does not change the campaign status. Results are per keyword.keywords: {ad_group_id, criterion_id, status}
set_ad_group_statusSets ad groups to ENABLED, PAUSED or REMOVED (up to 50). REMOVED cannot be undone. Does not change the campaign status. An enabled ad group spends only when its campaign is already enabled.ad_groups: {ad_group_id, status}
add_campaign_negative_keywordsAdds negative keywords on one campaign, in Google notation (up to 500). They apply to every ad group, including ad groups created later. Existing negatives are exists.campaign_id, keywords
remove_negative_keywordsRemoves negative keywords (up to 300). Only a negative keyword is removed. A location criterion is refused (update_campaign_locations); a language criterion is refused (update_campaign). REMOVED cannot be undone.items: {level, parent_id, criterion_id}. level is account, campaign, ad_group or shared_list. For account, parent_id may be omitted
add_shared_negative_keywordsAppends negative keywords to an existing shared list (up to 500). Does not create a list. The account-level list is refused. The preview names campaigns the list is already attached to.shared_list_id, keywords
remove_shared_negative_keywordsRemoves keywords from an existing shared list by criterion id (up to 300). REMOVED cannot be undone.shared_list_id, criterion_ids
set_url_optionsSets custom parameters, a tracking template and a final URL suffix on campaigns, ad groups or ads. Custom parameters merge by key (an empty value removes that key). A tracking template set here replaces the account-level template for that target. Does not change bids, budgets or status.level (campaign, ad_group or ad), targets (up to 20: id, custom_parameters, tracking_template, final_url_suffix)

level for asset tools is customer (all campaigns), campaign or ad_group; add_images accepts only campaign and ad_group; add_business_name and add_business_logo accept only customer and campaign. set_url_options accepts campaign, ad_group or ad. Asset tools accept up to 20 items and 20 targets per call; assets added to running campaigns start serving after Google reviews them.

Tracking which campaign a click came from

When the Google Ads account already has a tracking template, set a custom parameter on each campaign, ad group or ad instead of copying that template onto every campaign. A template such as {lpurl}?utm_campaign={_myname}&utm_term={keyword}&utm_ag={_groupname}&utm_ad={_adname} reads {_myname} from the campaign. The parameter name you store is myname (no underscore); myname, _myname and {_myname} are the same key. Set _groupname at ad group level and _adname at ad level the same way.

{
  "customer_id": "123-456-7890",
  "level": "campaign",
  "targets": [
    { "id": 111, "custom_parameters": [{ "key": "myname", "value": "s-en-us" }] }
  ]
}

Other custom parameters already on that campaign stay. Pass "value": "" to remove a key. Omit tracking_template and final_url_suffix to leave them; pass "" to clear one. Setting a tracking template here replaces the account-level template for that target, and the preview says so. Preview first, then confirm with the preview_id. Check url_options with get_campaign_setup (ads also list it in get_ads). One call changes every target or none of them. Changing URL options on an ad can send that ad back to review.

AI ad group drafts

draft_ad_groups drafts ad groups for an existing Search campaign: ADM analyzes the landing page, groups the keywords by theme and generates responsive search ads for each group. It changes nothing in Google Ads and needs a read and write key and a paid plan. The draft runs in the background, so the call returns a draft_id at once.

ToolWhat it doesKey parameters
draft_ad_groupsStarts a draft and returns draft_id, status and poll_after_seconds.campaign_id, landing_page_url, keywords (1 to 200), language, paused_keywords, group_name_prefix (up to 100 characters), ads_per_group (1 to 3, default 1)
get_ad_group_draftThe state of a draft: pending, running, done or failed. When done: ad_groups, primary_ad_group and warnings. When failed: error.draft_id

How a draft is used:

  1. Call draft_ad_groups, then call get_ad_group_draft every poll_after_seconds until status is done or failed. A draft usually finishes within 1 to 3 minutes.
  2. Pass ad_groups verbatim to add_ad_groups and preview it. If add_ad_groups is not available or can_write is false, the draft is the final result: present the ad groups to the user. Group names carry the prefix and the language tag.
  3. primary_ad_group names the group with the most keywords. It also receives the paused keywords, any keyword left unassigned by the draft, and the keywords of extra groups when the draft contains more than 20 groups. Add hand-written ads (add_ads) and ad group negative keywords (add_keywords) only to this group.

Drafts are kept for 24 hours. Each draft runs several AI calls; the limit is 60 drafts per hour per ADM account, shared with draft_campaign. Do not repeat a call for the same input while its draft is pending or running.

AI drafts of a new campaign

draft_campaign drafts a new Search campaign from a landing page. ADM reads the page, explores keywords or uses the ones you pass, groups them by theme and writes responsive search ads for each group. It changes nothing in Google Ads. When the draft is done, pass campaign verbatim to create_search_campaign (preview first). It needs a read and write key and a paid plan, and it shares the hourly draft limit with draft_ad_groups. Creating the campaign still uses the monthly campaign quota, which is checked again by create_search_campaign.

ToolWhat it doesKey parameters
draft_campaignStarts a draft and returns draft_id, status, poll_after_seconds and explore_keywords.landing_page_url. Optional: keywords (0 to 200; omit to explore), explore_keywords (default false when keywords are set; ignored when they are omitted), language (default en), country_code (default US), daily_budget, bidding (default MAXIMIZE_CONVERSIONS), campaign_name, business_goal (SALES, LEADS or TRAFFIC, default SALES; it only guides the AI), ads_per_group (1 to 3, default 1)
get_campaign_draftThe state of a draft: pending, running, done or failed. When done: campaign, missing_fields, warnings and strategy_notes. When failed: error.draft_id

How a new-campaign draft is used:

  1. Call draft_campaign. Omit keywords to let ADM explore keywords from the landing page. Pass keywords to use only those keywords, or also set explore_keywords to true to keep them and let ADM add more. Then call get_campaign_draft every poll_after_seconds until status is done or failed. A draft usually finishes within 1 to 3 minutes. Do not start another draft for the same input while one is pending or running.
  2. On failed, report error and start a new draft only if the user agrees. On done, show the user the campaign name, the ad groups and any warnings and strategy_notes.
  3. If missing_fields is not empty, ask the user for each one before creating the campaign. campaign.daily_budget is left empty when you did not pass daily_budget; do not invent a budget. Set the value on campaign after the user answers.
  4. Pass campaign verbatim to create_search_campaign and preview it. If create_search_campaign is not available or can_write is false, the draft is the final result: present the plan to the user, who can create the campaign in the ADM web app. The draft already targets Google Search only, with the country from country_code (default US) and the bidding you passed (default Maximize conversions). Confirm with the preview_id after the user approves. The new campaign stays paused.
  5. Verify with get_campaign_setup.

Drafts are kept for 24 hours. A draft id from draft_ad_groups is not visible to get_campaign_draft, and the reverse is also true.

Image uploads

add_images links images that were uploaded to ADM first. Upload each file as multipart/form-data in the field file, with the same API key:

curl -F [email protected] -H "Authorization: Bearer adm_xxx" https://app.adm.cc/mcp/uploads

In PowerShell, use curl.exe with the same arguments.

The upload accepts JPG and PNG files up to 5120 KB; the format is detected from the file content, not the file name. The add_images preview then checks the dimensions: square 1:1 at least 300×300, or landscape 1.91:1 at least 600×314, with 1% tolerance on the aspect ratio. Uploads need a read and write key and a paid plan, and each upload counts as one write call.

Response (HTTP 200):

{
  "upload_id": "upl_...",
  "width": 1200,
  "height": 628,
  "bytes": 184320,
  "sha256": "...",
  "content_type": "image/jpeg",
  "expires_at": "2026-10-01T09:30:00+00:00"
}

An upload_id is valid for 24 hours (expires_at) and only for the ADM account that uploaded it. After it expires, add_images returns not_found: upload the file again and preview again with the new upload_id.

Errors return {"error": {...}} with code, message, field_path, fix and request_id:

HTTP statuscodeCause
400invalid_argumentThe request is not multipart/form-data, the file field is missing, or the file is empty
401unauthorizedThe API key is missing or invalid
403permission_deniedThe key is read-only, or the plan does not include write access
404noneWrong URL (the endpoint is POST https://app.adm.cc/mcp/uploads on the app. subdomain), or uploads are not yet available on hosted ADM
413invalid_argumentThe file is larger than 5120 KB
415invalid_argumentThe file is not a JPG or PNG image
429quota_exceededDaily safety limit reached (UTC)

Google shows image assets only for eligible accounts, for example accounts that have been open for at least 60 days, have recent Search spend and a good policy history, and are not in a sensitive vertical. For other accounts Google may reject the link, or the images may not serve. Google also deduplicates images by content: an identical image already in the account is reused, and one already linked to the target is reported as exists.

Validation limits

ItemLimit
Ad groups per call20
Ads per ad group3
Keywords300 per ad group, 2,000 per call; each up to 80 characters and 10 words
Negative keywords1,000 per list or campaign
Locations50 per campaign; each is either country_code (ISO 3166-1 alpha-2) or location_id; at least one must be a positive target
Shared lists per create_search_campaign call20
Headlines3 to 15 per ad, display width up to 30
Descriptions2 to 4 per ad, display width up to 90
Display pathpath1 and path2, display width up to 15 each
Namesup to 255 characters
ImagesJPG or PNG up to 5120 KB; 1:1 at least 300×300 or 1.91:1 at least 600×314 (1% tolerance)
AI drafts0 to 200 keywords per draft (a new-campaign draft may omit keywords), 1 to 3 ads per group

Display width counts CJK characters as 2. Emoji are rejected. Pins use pinned_field values HEADLINE_1 to HEADLINE_3, DESCRIPTION_1 and DESCRIPTION_2.

Bidding type is one of MANUAL_CPC, MAXIMIZE_CLICKS, MAXIMIZE_CONVERSIONS, MAXIMIZE_CONVERSION_VALUE, TARGET_CPA and TARGET_ROAS. MANUAL_CPC requires max_cpc on the campaign or on each ad group; with MAXIMIZE_CLICKS, max_cpc is the CPC cap. network_settings.google_search must be true.

campaign.languages is accepted but not applied: Search campaigns are created without campaign-level language targeting. Use the ad group language field to record the ad language.

7. Error codes

Errors return one structure:

{
  "code": "invalid_argument",
  "message": "What went wrong",
  "field_path": "campaign.ad_groups[0].ads[0].headlines[3].text",
  "fix": "How to correct it",
  "retryable": false,
  "retry_after_seconds": null,
  "action_url": null,
  "request_id": "..."
}
CodeMeaningWhat to do
invalid_argumentA field is missing, out of range or too longCorrect every field listed in field_path and preview again
account_not_foundThe customer_id is not connected to your ADM accountUse a customer_id from get_accounts, or connect the account in ADM
not_foundThe campaign, ad group, list or operation does not existLook up the ID again with a read tool
permission_deniedThe key is read-only, or the plan does not include write accessCreate a read and write key, or upgrade the plan (action_url)
quota_exceededNo campaign creations left this month on your plan, or a daily safety limit was reachedUpgrade the plan (monthly creations) or wait for the reset in the error
preview_requiredconfirm: true or confirm_preview used a preview_id that was never confirmed and has no stored preview from the last 30 minutesPreview again, get approval, then confirm with the new preview_id. If this preview_id was already confirmed, call confirm_preview again; that returns the stored result
conflictA resource with the same name but different content already existsCheck the existing resource; do not rename and recreate
busyAnother write to this Google Ads account is runningWait retry_after_seconds, then retry
reauth_requiredGoogle authorization for ADM has expiredReconnect Google Ads in ADM (action_url)
google_ads_errorGoogle Ads rejected the change (policy or validation)Read the Google reason in message and adjust
result_unknownThe call timed out or was interrupted and the outcome is not knownCall get_operation or get_campaign_setup first; if the change is missing, preview again and confirm with the new preview_id after approval
internalServer errorRetry once; contact support with request_id if it repeats

HTTP 401 (unauthorized) is returned before any tool runs when the key is missing or invalid. See the troubleshooting table in section 4.

Every tool call returns within 45 seconds. Large reads can be narrowed with campaign_id.

8. Quotas

QuotaFreeStarterProfessionalEnterprise
Read toolsYesYesYesYes
Write toolsNoYesYesYes
Campaign creations per month1 (web only)1030100
Write calls per day (UTC)Not available3,0003,0003,000
AI drafts per hourNot available606060
  • The monthly creation count is shared with campaigns created in the ADM web wizard and resets on your billing date. get_accounts shows the remaining count and the reset time. When it is used up, the error names your plan, its monthly count and the next plan's count; upgrading lifts the limit right away.
  • A write call is any call with confirm: true, plus each image upload. Previews do not count.
  • Daily limits (write calls, write items, preview items and Google Ads queries) are safety limits against runaway automation, set far above normal use and the same on every paid plan. They reset at 00:00 UTC.
  • AI drafts (draft_ad_groups and draft_campaign) share one hourly limit and do not count as write calls. draft_campaign does not use a monthly campaign creation by itself; create_search_campaign does.
  • Write access needs both a read and write key and an active paid plan.

9. Why write tools should not be auto-approved

Several read tools return text written by other people: search terms are typed by anyone who sees your ads, and ad text, asset text and names may come from colleagues or agencies. A search term can contain words that look like an instruction to the AI tool. ADM labels this content as data and asks the AI tool to ignore instructions inside it, but no AI model is guaranteed to follow that rule every time.

The approval prompt for write tools is the step where you see exactly what will change. Keep it in place:

  • Approve only read tools automatically. In Claude Code, allow only mcp__adm__get_*; do not add mcp__adm__* or individual write tools to the allow list.
  • Do not run the AI tool with approval prompts turned off while the ADM server is connected.
  • Read each preview before approving, especially budgets, update_ads (editing ad text sends the ad back to review) and calls that set a status to ENABLED (set_campaign_status, update_ad_group, set_ad_group_status, set_ad_status, set_keyword_status). update_campaign cannot enable a campaign. REMOVED cannot be undone.

10. Key rotation

  1. Create a new key on the API keys page.
  2. Replace the key: update ADM_API_KEY, or the key in the AI tool's MCP configuration, then restart the AI tool. Claude Code stores the header when the server is added, so run claude mcp remove adm and add the server again (section 4).
  3. Repeat step 2 on each computer that uses the old key.
  4. Watch the "Last used" time of the old key. When it no longer changes, revoke the old key.

Treat a key like a password. Do not commit it to a repository or write it into project files. A key pasted into a chat remains in the conversation history; rotate it if that history may be seen by others. If a key may have leaked, revoke it immediately.

11. Changes you make in Google Ads

The tools create and add. They can also pause a campaign, change its name, daily budget, bidding and (except on Search) languages, add or remove its locations, set device bid adjustments (including excluding mobile), create, attach and retarget a shared portfolio bidding strategy, rename, pause, enable, rebid or remove an ad group, enable, pause or remove ads and positive keywords, edit the headlines, descriptions, final URL and paths of an existing responsive search ad, add or remove negative keywords, pause or unlink assets, and set URL options (custom parameters, tracking template, final URL suffix) on a campaign, ad group or ad. Enabling a campaign is only set_campaign_status. REMOVED cannot be undone. Make these changes in Google Ads directly:

  • Delete an unused asset from the asset library (unlinking it is set_asset_link_status)
  • Set language targeting on a Search campaign (Google removed that in September 2026; the campaign targets all languages)
  • Turn off auto-apply recommendations (Recommendations, then Auto-apply)
  • Set up conversion tracking
  • Add lead form, business message, mobile app and location assets
  • Set the ad schedule and audience segments
  • Create campaign types other than Search, such as Performance Max
  • Manage billing and complete advertiser verification

12. Changelog

2026-10-08

  • Hosted ADM now offers the read tools and AI drafts (draft_campaign, draft_ad_groups). The write tools are not yet available there; when they are missing, a draft is presented as a plan instead of being created.
  • get_accounts accepts skill_version and returns skill_latest_version, skill_update_hint, upgrade_hint and upgrade_url (new fields only, no breaking change; contract version stays 2026-10-03). Skill file version 2026-10-08: see Update the skill.

2026-10-05

  • Added (new tools only, no breaking change): add_promotions, add_calls, add_business_name, add_business_logo. get_assets types also accepts promotion, call, business_name and business_logo, and set_asset_link_status pauses, enables or unlinks them.

2026-10-04

  • Added: get_ad_groups, get_paused_summary, find_negative_conflicts, get_conversion_actions, get_customizers, confirm_preview, set_location_bid_adjustments, set_customizer_values. get_campaigns now includes target CPA, target ROAS and the portfolio strategy. get_assets filters by level, types, asset_ids and link_status and pages with limit / cursor. get_keywords filters by status, match_type and text_contains. get_ads adds text_contains, compact and a performance_label on each headline and description. get_location_performance includes bid_adjustment_percent for a targeted location.
  • Behavior change: get_campaign_setup no longer returns assets unless include contains assets. The default include is ad_groups, keywords and ads. Ads include ad_strength.
  • set_campaign_status accepts up to 50 campaigns and the preview totals the daily budget of campaigns that are not already enabled. Enabling a manual CPC campaign warns when an ad group max CPC is still a placeholder (0.05 or less). update_ads accepts headline_replacements and description_replacements. add_prices rejects unsupported languages and currencies in the preview. add_structured_snippets checks the header against the official translation for the language you pass. Keyword write previews include campaign_name and ad_group_name. After approval, confirm_preview runs a preview from its preview_id alone.
  • Added (new tools only, no breaking change): get_device_performance, set_device_bid_adjustments, get_location_performance. get_campaign_setup now includes device_bid_adjustments on the campaign and each ad group. Excluding a location still uses update_campaign_locations.
  • Added (new tools only, no breaking change): update_campaign_locations, set_asset_link_status, get_bidding_strategies, create_bidding_strategy, attach_bidding_strategy, update_bidding_strategy.
  • update_campaign no longer rejects a portfolio strategy. Passing bidding detaches the campaign from the portfolio and the preview names the portfolio it leaves.

2026-10-03

  • Contract version 2026-10-03. Breaking change: update_campaign status accepts only PAUSED. Enabling a campaign is only set_campaign_status, after an explicit request. REMOVED cannot be undone.
  • Added: set_ad_status, set_keyword_status, set_ad_group_status, add_campaign_negative_keywords, remove_negative_keywords, add_shared_negative_keywords, remove_shared_negative_keywords. Batch results are per item; a failure does not undo items that succeeded.
  • Added update_ads (new tool only, no breaking change; contract version stays 2026-10-03): edit a responsive search ad in place. The ad id and its performance history stay. Do not remove the ad and add a replacement.
  • Extended update_campaign with daily_budget (rejected on a shared budget), bidding (rejected on a portfolio strategy) and languages (empty list means all languages; Search campaigns are rejected and nothing else in that call is changed). Extended update_ad_group with max_cpc. get_campaign_setup keywords now include id (the criterion id).
  • Added in contract version 2026-09-30 (new tools only, no breaking change): draft_campaign and get_campaign_draft. ADM drafts a new Search campaign from a landing page, including keyword exploration, grouping and responsive search ads. Pass the finished campaign to create_search_campaign. The hourly draft limit is shared with draft_ad_groups.
  • Added in contract version 2026-09-30 (new tools only, no breaking change): write tools update_campaign and update_ad_group. Rename a campaign or ad group and/or pause or enable it in one atomic change, for example to mark why it was paused. set_campaign_status keeps working unchanged.
  • Added in contract version 2026-09-30 (new tool and fields only, no breaking change): write tool set_url_options. Set custom parameters (merged by key), a tracking template and a final URL suffix on campaigns, ad groups or ads, so an account-level tracking template can record which one a click came from. get_campaign_setup and get_ads return url_options when any of the three is set.

2026-09-30

  • First release of the ADM MCP server, contract version 2026-09-30.
  • 9 read tools: get_accounts, get_campaigns, get_campaign_setup, get_ads, get_keywords, get_search_terms, get_negative_keywords, get_assets, get_operation.
  • 11 write tools: create_search_campaign, add_ad_groups, add_keywords, add_account_negative_keywords, create_shared_negative_list, attach_shared_negative_list, add_sitelinks, add_callouts, add_structured_snippets, add_prices, set_campaign_status.
  • ADM skill adm-google-ads for building campaigns from a plan (section 3).
  • Added in the same contract version (new tools and fields only, no breaking change): write tools add_ads and add_images, AI draft tools draft_ad_groups and get_ad_group_draft, the image upload endpoint POST /mcp/uploads, and image assets in get_assets and get_campaign_setup. The skill adds the AI draft and image workflows.
  • Documentation moved to /docs/google-ads-skill. The skill adds reports and questions, a default account saved per project, and setup for Codex, Cursor and Windsurf; the server instructions add the same default account rule. On first use in a project, the skill suggests prompts the user can copy.