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 nameadm-google-ads) - MCP server:
https://app.adm.cc/mcp(Streamable HTTP), authenticated with an ADM API key sent asAuthorization: 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 for | ADM tools |
|---|---|
| Choose or switch the Google Ads account | get_accounts |
| Reports and questions about performance | get_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 tablet | set_device_bid_adjustments |
| See which locations spent and converted | get_location_performance, set_location_bid_adjustments |
| Confirm a preview with only its id | confirm_preview |
| Build campaigns from a plan | create_search_campaign, add_ad_groups, add_keywords, add_ads |
| Negative keywords | add_account_negative_keywords, create_shared_negative_list, attach_shared_negative_list |
| Sitelinks, callouts, structured snippets, prices, promotions and calls | add_sitelinks, add_callouts, add_structured_snippets, add_prices, add_promotions, add_calls |
| Business name | add_business_name |
| Image assets and business logos | Upload endpoint POST https://app.adm.cc/mcp/uploads, then add_images or add_business_logo |
| Ad groups drafted by ADM AI | draft_ad_groups, get_ad_group_draft |
| A new campaign drafted by ADM AI | draft_campaign, get_campaign_draft, then create_search_campaign |
| Check a build and resume after an interruption | get_campaign_setup, get_operation |
| Enable or pause a campaign | set_campaign_status |
| Pause a campaign, or change its name, daily budget, bidding or languages | update_campaign |
| Add or remove targeted or excluded locations | update_campaign_locations |
| Create, attach or change a shared portfolio bidding strategy | get_bidding_strategies, create_bidding_strategy, attach_bidding_strategy, update_bidding_strategy |
| Pause or unlink an asset | set_asset_link_status |
| Rename, pause, enable or rebid an ad group | update_ad_group, set_ad_group_status |
| Edit the text of an existing ad | update_ads |
| Enable, pause or remove ads or keywords | set_ad_status, set_keyword_status |
| Add or remove negative keywords | add_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 suffix | set_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 groupphoto-enhancer-DE - Landing page
https://www.example.com/de/photo-enhancer, display pathfoto/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 asphoto-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_keywordsonce per account,create_shared_negative_listonce per list, thenattach_shared_negative_listfor 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
| Symptom | Cause | Fix |
|---|---|---|
| HTTP 401 | The key is missing, mistyped, or has leading or trailing spaces | Check ADM_API_KEY, restart the AI tool, or remove the server and add it again with the key pasted exactly as shown |
| HTTP 401 | The key was revoked on the API keys page | Create a new key and update the configuration (section 10) |
| HTTP 401 on Windows | The header used a PowerShell variable inside single quotes, so the variable was not expanded | Use double quotes, or paste the key directly into the command |
| HTTP 401 after setting the variable | The AI tool was started before the variable was set | Quit 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 404 | The URL is wrong | The URL must be exactly https://app.adm.cc/mcp: https, the app. subdomain, and the /mcp path |
| Timeout or connection refused | A corporate proxy or firewall blocks the request | Set 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.
- 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 namedadmalready exists, replace it.
| AI tool | How to add the server |
|---|---|
| Claude Code | Run claude mcp add --transport http --scope user adm https://app.adm.cc/mcp --header "Authorization: Bearer <key>" |
| Codex | In ~/.codex/config.toml add [mcp_servers.adm] with url = "https://app.adm.cc/mcp" and http_headers = { "Authorization" = "Bearer <key>" } |
| Cursor | In ~/.cursor/mcp.json add "adm": { "url": "https://app.adm.cc/mcp", "headers": { "Authorization": "Bearer <key>" } } under mcpServers |
| Windsurf | In ~/.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.
- Save the skill. Download
https://adm.cc/docs/google-ads-skill/SKILL.mdtoadm-google-ads/SKILL.mdinside the skills directory you read for all projects. The folder name must beadm-google-ads. Create missing directories. If you do not know that directory, use the project-level one:
| AI tool | Skill 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 |
- Test the connection. Call
get_accountson theadmserver. If the tool is not available until you restart, say so. - 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
| Tool | What it returns | Key parameters |
|---|---|---|
get_accounts | Connected 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_campaigns | Campaigns with settings and performance, sorted by cost. Removed campaigns are never returned. | days, status (ENABLED or PAUSED), limit, cursor |
get_campaign_setup | The 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_ads | Responsive 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_keywords | Keywords 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_terms | Search 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_keywords | Negative 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_assets | Sitelink, 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_strategies | Portfolio (shared) bidding strategies: id, name, type, target CPA or ROAS, CPC ceiling, and the campaigns on each. | none |
get_device_performance | Performance 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_performance | Performance 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_groups | Ad groups with status, campaign, max CPC, bidding, keyword count and ad count. | campaign_id, status, limit, cursor |
get_paused_summary | How many campaigns, ad groups, keywords and ads are paused, with ids and names (200 per level). | none |
find_negative_conflicts | Positive 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_actions | Conversion 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_customizers | Ad 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_operation | The 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.
| Tool | What it does | Key parameters |
|---|---|---|
create_search_campaign | Creates 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_groups | Adds ad groups with keywords, negatives and ads to an existing Search campaign. Created paused unless status is ENABLED. | campaign_id, ad_groups, status |
add_keywords | Adds 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_ads | Adds 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_ads | Edits 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_keywords | Adds negatives to the account-level list, which blocks them in every Search campaign, including running ones. | keywords (up to 500) |
create_shared_negative_list | Creates 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_list | Attaches an existing shared list to campaigns. | shared_list_id, campaign_ids |
add_sitelinks | Adds 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_callouts | Adds callouts, up to 25 each. | level, callouts, campaign_ids or ad_group_ids |
add_structured_snippets | Adds 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_prices | Adds 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_promotions | Adds 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_calls | Adds 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_name | Adds 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_logo | Adds 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_images | Adds 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_status | Pauses 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_preview | Runs 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_adjustments | Sets 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_values | Creates 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_campaign | In 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_locations | Adds 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_adjustments | Sets 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_status | Pauses, 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_strategy | Creates 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_strategy | Attaches 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_strategy | Renames 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_group | Renames 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_status | Sets 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_status | Sets 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_status | Sets 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_keywords | Adds 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_keywords | Removes 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_keywords | Appends 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_keywords | Removes keywords from an existing shared list by criterion id (up to 300). REMOVED cannot be undone. | shared_list_id, criterion_ids |
set_url_options | Sets 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.
| Tool | What it does | Key parameters |
|---|---|---|
draft_ad_groups | Starts 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_draft | The 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:
- Call
draft_ad_groups, then callget_ad_group_drafteverypoll_after_secondsuntilstatusisdoneorfailed. A draft usually finishes within 1 to 3 minutes. - Pass
ad_groupsverbatim toadd_ad_groupsand preview it. Ifadd_ad_groupsis not available orcan_writeis false, the draft is the final result: present the ad groups to the user. Group names carry the prefix and the language tag. primary_ad_groupnames 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.
| Tool | What it does | Key parameters |
|---|---|---|
draft_campaign | Starts 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_draft | The 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:
- Call
draft_campaign. Omitkeywordsto let ADM explore keywords from the landing page. Passkeywordsto use only those keywords, or also setexplore_keywordsto true to keep them and let ADM add more. Then callget_campaign_drafteverypoll_after_secondsuntilstatusisdoneorfailed. A draft usually finishes within 1 to 3 minutes. Do not start another draft for the same input while one is pending or running. - On
failed, reporterrorand start a new draft only if the user agrees. Ondone, show the user the campaign name, the ad groups and anywarningsandstrategy_notes. - If
missing_fieldsis not empty, ask the user for each one before creating the campaign.campaign.daily_budgetis left empty when you did not passdaily_budget; do not invent a budget. Set the value oncampaignafter the user answers. - Pass
campaignverbatim tocreate_search_campaignand preview it. Ifcreate_search_campaignis not available orcan_writeis 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 fromcountry_code(defaultUS) and the bidding you passed (default Maximize conversions). Confirm with thepreview_idafter the user approves. The new campaign stays paused. - 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 status | code | Cause |
|---|---|---|
| 400 | invalid_argument | The request is not multipart/form-data, the file field is missing, or the file is empty |
| 401 | unauthorized | The API key is missing or invalid |
| 403 | permission_denied | The key is read-only, or the plan does not include write access |
| 404 | none | Wrong URL (the endpoint is POST https://app.adm.cc/mcp/uploads on the app. subdomain), or uploads are not yet available on hosted ADM |
| 413 | invalid_argument | The file is larger than 5120 KB |
| 415 | invalid_argument | The file is not a JPG or PNG image |
| 429 | quota_exceeded | Daily 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
| Item | Limit |
|---|---|
| Ad groups per call | 20 |
| Ads per ad group | 3 |
| Keywords | 300 per ad group, 2,000 per call; each up to 80 characters and 10 words |
| Negative keywords | 1,000 per list or campaign |
| Locations | 50 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 call | 20 |
| Headlines | 3 to 15 per ad, display width up to 30 |
| Descriptions | 2 to 4 per ad, display width up to 90 |
| Display path | path1 and path2, display width up to 15 each |
| Names | up to 255 characters |
| Images | JPG or PNG up to 5120 KB; 1:1 at least 300×300 or 1.91:1 at least 600×314 (1% tolerance) |
| AI drafts | 0 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": "..."
}
| Code | Meaning | What to do |
|---|---|---|
invalid_argument | A field is missing, out of range or too long | Correct every field listed in field_path and preview again |
account_not_found | The customer_id is not connected to your ADM account | Use a customer_id from get_accounts, or connect the account in ADM |
not_found | The campaign, ad group, list or operation does not exist | Look up the ID again with a read tool |
permission_denied | The key is read-only, or the plan does not include write access | Create a read and write key, or upgrade the plan (action_url) |
quota_exceeded | No campaign creations left this month on your plan, or a daily safety limit was reached | Upgrade the plan (monthly creations) or wait for the reset in the error |
preview_required | confirm: true or confirm_preview used a preview_id that was never confirmed and has no stored preview from the last 30 minutes | Preview 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 |
conflict | A resource with the same name but different content already exists | Check the existing resource; do not rename and recreate |
busy | Another write to this Google Ads account is running | Wait retry_after_seconds, then retry |
reauth_required | Google authorization for ADM has expired | Reconnect Google Ads in ADM (action_url) |
google_ads_error | Google Ads rejected the change (policy or validation) | Read the Google reason in message and adjust |
result_unknown | The call timed out or was interrupted and the outcome is not known | Call get_operation or get_campaign_setup first; if the change is missing, preview again and confirm with the new preview_id after approval |
internal | Server error | Retry 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
| Quota | Free | Starter | Professional | Enterprise |
|---|---|---|---|---|
| Read tools | Yes | Yes | Yes | Yes |
| Write tools | No | Yes | Yes | Yes |
| Campaign creations per month | 1 (web only) | 10 | 30 | 100 |
| Write calls per day (UTC) | Not available | 3,000 | 3,000 | 3,000 |
| AI drafts per hour | Not available | 60 | 60 | 60 |
- The monthly creation count is shared with campaigns created in the ADM web wizard and resets on your billing date.
get_accountsshows 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_groupsanddraft_campaign) share one hourly limit and do not count as write calls.draft_campaigndoes not use a monthly campaign creation by itself;create_search_campaigndoes. - 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 addmcp__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_campaigncannot enable a campaign.REMOVEDcannot be undone.
10. Key rotation
- Create a new key on the API keys page.
- 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 runclaude mcp remove admand add the server again (section 4). - Repeat step 2 on each computer that uses the old key.
- 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_accountsacceptsskill_versionand returnsskill_latest_version,skill_update_hint,upgrade_hintandupgrade_url(new fields only, no breaking change; contract version stays2026-10-03). Skill file version2026-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_assetstypesalso acceptspromotion,call,business_nameandbusiness_logo, andset_asset_link_statuspauses, 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_campaignsnow includes target CPA, target ROAS and the portfolio strategy.get_assetsfilters bylevel,types,asset_idsandlink_statusand pages withlimit/cursor.get_keywordsfilters bystatus,match_typeandtext_contains.get_adsaddstext_contains,compactand aperformance_labelon each headline and description.get_location_performanceincludesbid_adjustment_percentfor a targeted location. - Behavior change:
get_campaign_setupno longer returns assets unlessincludecontainsassets. The default include isad_groups,keywordsandads. Ads includead_strength. set_campaign_statusaccepts 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_adsacceptsheadline_replacementsanddescription_replacements.add_pricesrejects unsupported languages and currencies in the preview.add_structured_snippetschecks the header against the official translation for the language you pass. Keyword write previews includecampaign_nameandad_group_name. After approval,confirm_previewruns a preview from itspreview_idalone.- Added (new tools only, no breaking change):
get_device_performance,set_device_bid_adjustments,get_location_performance.get_campaign_setupnow includesdevice_bid_adjustmentson the campaign and each ad group. Excluding a location still usesupdate_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_campaignno longer rejects a portfolio strategy. Passingbiddingdetaches the campaign from the portfolio and the preview names the portfolio it leaves.
2026-10-03
- Contract version
2026-10-03. Breaking change:update_campaignstatusaccepts onlyPAUSED. Enabling a campaign is onlyset_campaign_status, after an explicit request.REMOVEDcannot 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 stays2026-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_campaignwithdaily_budget(rejected on a shared budget),bidding(rejected on a portfolio strategy) andlanguages(empty list means all languages; Search campaigns are rejected and nothing else in that call is changed). Extendedupdate_ad_groupwithmax_cpc.get_campaign_setupkeywords now includeid(the criterion id). - Added in contract version
2026-09-30(new tools only, no breaking change):draft_campaignandget_campaign_draft. ADM drafts a new Search campaign from a landing page, including keyword exploration, grouping and responsive search ads. Pass the finishedcampaigntocreate_search_campaign. The hourly draft limit is shared withdraft_ad_groups. - Added in contract version
2026-09-30(new tools only, no breaking change): write toolsupdate_campaignandupdate_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_statuskeeps working unchanged. - Added in contract version
2026-09-30(new tool and fields only, no breaking change): write toolset_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_setupandget_adsreturnurl_optionswhen 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-adsfor building campaigns from a plan (section 3). - Added in the same contract version (new tools and fields only, no breaking change): write tools
add_adsandadd_images, AI draft toolsdraft_ad_groupsandget_ad_group_draft, the image upload endpointPOST /mcp/uploads, and image assets inget_assetsandget_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.