使用说明
MCP Google Ads Server
A Model Context Protocol (MCP) server for Google Ads API integration. This server allows AI assistants like Claude to interact with Google Ads data through standardized MCP tools.
Features
- Campaign Management: Retrieve campaigns with performance metrics
- Ad Groups & Keywords: Fetch ad groups, keywords, and search terms
- Performance Analytics: Geographic, demographic, and time-based performance data
- Account Management: List accounts, get customer details
- Asset Management: Access ads, creatives, and assets
- Write Operations: Create and update budgets, campaigns, ad groups, keywords and ads — opt-in, with dry-run and spend caps
- Dual Transport: Supports both HTTP and stdio modes
Quick Start
# Clone the repository
git clone https://github.com/ameydabhade/google-ads-mcp.git
cd google-ads-mcp
# Install dependencies
npm install
# Configure credentials
cp .env.example .env
# Edit .env with your Google Ads API credentials
# Build and start
npm run build
npm run start:server
Prerequisites
Before using this MCP server, you need:
- Google Cloud Project with Google Ads API enabled
- OAuth 2.0 Credentials (Client ID and Secret)
- Google Ads Developer Token (Test or Production)
- Google Ads Account to query
Setup Guide
Step 1: Create Google Cloud Project
- Go to Google Cloud Console
- Create a new project or select an existing one
- Enable the Google Ads API:
- Navigate to "APIs & Services" > "Library"
- Search for "Google Ads API"
- Click "Enable"
Step 2: Create OAuth 2.0 Credentials
- Go to "APIs & Services" > "Credentials"
- Click "Create Credentials" > "OAuth 2.0 Client IDs"
- Select "Web application" as the application type
- Under "Authorized redirect URIs", add
http://localhost:8085(this is where the helper script in Step 4 receives the authorization code) - Note your Client ID and Client Secret
Step 3: Get Developer Token
- Sign in to Google Ads
- Go to "Tools & Settings" > "API Center"
- If you don't have a developer token, apply for one
- For testing, use the "Test Account" developer token
- For production, apply for Basic or Standard access
Step 4: Generate Refresh Token
A developer token on its own is not enough to call the Google Ads API — every
request also needs an OAuth token, even for a single-user setup. This is a
one-time step: you generate one refresh token, put it in .env, and reuse it
indefinitely.
Put your client ID and secret in .env first, then run:
npm run oauth:generate
The script opens your browser, captures the authorization code on
http://localhost:8085, exchanges it, and prints the refresh token to paste
into .env. If port 8085 is taken, set OAUTH_HELPER_PORT to a free port (and
add the matching redirect URI in the Google Cloud console).
Doing it manually
- Build an authorization URL (the redirect URI must match one registered on your OAuth client):
https://accounts.google.com/o/oauth2/v2/auth?client_id=YOUR_CLIENT_ID&redirect_uri=http://localhost:8085&scope=https://www.googleapis.com/auth/adwords&response_type=code&access_type=offline&prompt=consent
- Visit it and authorize access
- Copy the
codequery parameter off the URL you land on - Exchange it for tokens:
curl -X POST https://oauth2.googleapis.com/token \
-d "code=YOUR_AUTH_CODE" \
-d "client_id=YOUR_CLIENT_ID" \
-d "client_secret=YOUR_CLIENT_SECRET" \
-d "redirect_uri=http://localhost:8085" \
-d "grant_type=authorization_code"
- Save the
refresh_tokenfrom the response
access_type=offline and prompt=consent are both required — without them
Google returns an access token but no refresh token. Note that the older
out-of-band flow (urn:ietf:wg:oauth:2.0:oob) was discontinued in 2022 and no
longer works.
Step 5: Configure Environment
cp .env.example .env
Edit .env with your credentials:
GOOGLE_ADS_CLIENT_ID=your_client_id.apps.googleusercontent.com
GOOGLE_ADS_CLIENT_SECRET=your_client_secret
GOOGLE_ADS_DEVELOPER_TOKEN=your_developer_token
GOOGLE_ADS_REFRESH_TOKEN=your_refresh_token
GOOGLE_ADS_CUSTOMER_ID=1234567890
Installation
# Install dependencies
npm install
# Build the project
npm run build
# Start the server (HTTP mode)
npm run start:server
# Or run in development mode
npm run dev:server
Usage
HTTP Server Mode
The server exposes an HTTP endpoint at http://localhost:3000/mcp that accepts JSON-RPC requests.
# Start the server
npm run start:server
# Test with curl
curl -X POST http://localhost:3000/mcp \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"method": "tools/list",
"id": 1
}'
Stdio Mode (Claude Desktop)
For use with Claude Desktop, add to your config:
{
"mcpServers": {
"google-ads": {
"command": "node",
"args": ["/path/to/mcp-google-ads/dist/index.js"],
"env": {
"GOOGLE_ADS_CLIENT_ID": "your_client_id",
"GOOGLE_ADS_CLIENT_SECRET": "your_client_secret",
"GOOGLE_ADS_DEVELOPER_TOKEN": "your_developer_token",
"GOOGLE_ADS_REFRESH_TOKEN": "your_refresh_token",
"GOOGLE_ADS_CUSTOMER_ID": "1234567890",
"GOOGLE_ADS_ENABLE_WRITES": "false"
}
}
}
}
Set GOOGLE_ADS_ENABLE_WRITES to "true" to expose the write tools — see
Write Operations.
Available Tools
Read Tools
| Tool | Description |
|---|---|
get_accounts |
List all accessible Google Ads accounts |
get_campaigns |
Get campaigns with performance metrics |
get_ad_groups |
Get ad groups with metrics |
get_keywords |
Get keywords with quality scores |
get_search_terms |
Get search queries that triggered ads, with the matching keyword and its criterion ID |
get_auction_insights |
Competitor domains in the same auctions (impression share, overlap, outranking) |
get_geographic_performance |
Performance by location |
get_audience_demographics |
Age and gender performance |
get_ad_schedule_performance |
Performance by day/hour |
get_account_summary |
Overall account metrics |
get_customer_details |
Account configuration |
get_campaign_details |
Single campaign config |
get_ad_group_details |
Single ad group config |
get_ad_details |
Ad creative details |
get_asset_by_id |
Asset (image/video) details |
get_user_list_details |
Audience list details |
get_conversion_action_details |
Conversion tracking config |
get_constants |
Reference data (geo, language, etc.) |
healthcheck |
Test API connectivity |
Write Tools
These change live campaigns and spend real money. They are disabled by default — see Write Operations.
| Tool | Description |
|---|---|
create_campaign_budget |
Create a campaign budget |
update_campaign_budget |
Change a budget's amount, name or delivery method |
create_campaign |
Create a campaign against an existing budget |
update_campaign |
Change name, status, dates, budget or bidding targets |
remove_campaigns |
Permanently remove campaigns |
create_ad_group |
Create an ad group in a campaign |
update_ad_group |
Change name, status or default CPC bid |
remove_ad_groups |
Permanently remove ad groups |
add_keywords |
Add positive keywords to an ad group |
update_keywords |
Change keyword status or bids |
remove_keywords |
Permanently remove keywords |
add_negative_keywords |
Add negative keywords at campaign or ad group level |
create_responsive_search_ad |
Create a responsive search ad |
update_ad_status |
Enable, pause or remove ads |
Auction Insights
get_auction_insights returns the same competitor view as the Auction Insights
report in the Google Ads UI, via segments.auction_insight_domain.
{
"name": "get_auction_insights",
"arguments": {
"date_range": { "start_date": "2026-01-01", "end_date": "2026-01-31" },
"level": "campaign",
"sort_by": "impression_share"
}
}
Rows are grouped by competitor domain, averaging each metric across the
campaigns (or ad groups) the domain appeared in; appears_in keeps the
per-campaign breakdown. Metrics come back as 0-1 fractions, with the same
figures as percentages under metrics_percent.
| Metric | Meaning |
|---|---|
impression_share |
Share of available impressions that competitor won |
overlap_rate |
How often they appeared when you also appeared |
position_above_rate |
How often they ranked above you when both appeared |
outranking_share |
How often you ranked above them, or showed when they didn't |
top_of_page_rate |
How often their ad appeared above the organic results |
absolute_top_of_page_rate |
How often their ad was the very first ad |
Constraints worth knowing: Auction Insights covers Search and Shopping
campaigns only, and your own account shows up as one of the domains. Google
restricts which segments can be combined with auction_insight_domain, so this
tool deliberately does not segment by date.
Finding and excluding wasted spend
get_search_terms reports the keyword that matched each search term, which
pairs with the write tools:
get_search_terms— find terms with cost and no conversions. Each term carries akeywordsarray withkeyword_text,match_typeandcriterion_id, ranked by impressions.add_negative_keywords— exclude the bad terms, campaign- or ad-group-wide.update_keywords— or pause the matching keyword outright using thecriterion_idfrom step 1.
Because the query is segmented by keyword, a single search term can be matched
by several keywords; all of them appear in keywords, while the scalar
keyword_text / match_type fields resolve to whichever drove the most
impressions.
Write Operations
Writes are opt-in. With GOOGLE_ADS_ENABLE_WRITES unset or false the write tools
are not advertised in tools/list at all, and calling one returns a
WRITE_DISABLED error.
# Required to expose the write tools
GOOGLE_ADS_ENABLE_WRITES=true
# Optional: make every write a dry run unless dry_run=false is passed
GOOGLE_ADS_WRITE_DRY_RUN_DEFAULT=false
# Optional spend ceilings in micros (1 unit = 1,000,000 micros)
GOOGLE_ADS_MAX_BUDGET_MICROS=100000000
GOOGLE_ADS_MAX_BID_MICROS=10000000
Toggling GOOGLE_ADS_ENABLE_WRITES takes effect on the next client reconnect,
since MCP clients cache the tool list.
Safety model
- Off by default. Nothing can be mutated until you opt in.
- Dry run. Every write tool accepts
dry_run: true, which maps to the Google Ads API'svalidate_onlyflag: the change is validated server-side but not applied. Dry runs return no resource names — that is the API's behaviour, not an error. - Atomic batches.
partial_failureis always false, so a batch either applies cleanly or fails entirely. You never get half a change. - Paused by default.
create_campaign,create_ad_groupandcreate_responsive_search_adcreate PAUSED entities unless you explicitly ask forENABLED, so nothing starts serving as a side effect. - Spend caps.
GOOGLE_ADS_MAX_BUDGET_MICROS/GOOGLE_ADS_MAX_BID_MICROSreject over-limit budgets and bids locally, before the API call. - Tool annotations. Write tools carry
readOnlyHint: false; the irreversible ones (remove_*,update_ad_status) carrydestructiveHint: trueso clients can prompt for confirmation.
Amounts
Monetary fields accept either micros or currency units — pass one, not both:
{ "amount_micros": 50000000 }
{ "amount": 50.00 }
Examples
Pause a campaign:
{
"method": "tools/call",
"params": {
"name": "update_campaign",
"arguments": { "campaign_id": "123456789", "status": "PAUSED" }
}
}
Validate a budget increase without applying it:
{
"method": "tools/call",
"params": {
"name": "update_campaign_budget",
"arguments": { "budget_id": "987654321", "amount": 75.00, "dry_run": true }
}
}
Build a paused search campaign end to end:
{ "name": "create_campaign_budget", "arguments": { "name": "Q1 Budget", "amount": 50.00 } }
{ "name": "create_campaign", "arguments": { "name": "Q1 Search", "budget_id": "<from above>",
"bidding_strategy": { "type": "TARGET_CPA", "target_cpa": 25.00 } } }
{ "name": "create_ad_group", "arguments": { "campaign_id": "<from above>", "name": "Shoes", "cpc_bid": 1.50 } }
{ "name": "add_keywords", "arguments": { "ad_group_id": "<from above>",
"keywords": [{ "text": "running shoes", "match_type": "PHRASE" }] } }
{ "name": "create_responsive_search_ad", "arguments": { "ad_group_id": "<from above>",
"final_urls": ["https://example.com"],
"headlines": ["Running Shoes", "Free Shipping", "Shop Today"],
"descriptions": ["Top brands at great prices.", "Free returns within 30 days."] } }
Add negative keywords across a whole campaign:
{
"name": "add_negative_keywords",
"arguments": {
"campaign_id": "123456789",
"keywords": [{ "text": "free", "match_type": "BROAD" }]
}
}
Write response format
{
"operation": "update_campaign",
"dry_run": false,
"customer_id": "1234567890",
"requested_count": 1,
"applied_count": 1,
"results": [
{ "resource_name": "customers/1234567890/campaigns/123456789", "id": "123456789" }
],
"metadata": { "timestamp": "2026-01-15T10:30:00.000Z" }
}
Tool Input Format
All tools accept credentials in this format:
{
"user_credentials": {
"refresh_token": "your_refresh_token",
"customer_id": "1234567890"
},
"date_range": {
"start_date": "2024-01-01",
"end_date": "2024-01-31"
}
}
If you've set GOOGLE_ADS_REFRESH_TOKEN and GOOGLE_ADS_CUSTOMER_ID in your environment, you can omit those fields.
Example Queries
Get all campaigns for last 30 days
{
"method": "tools/call",
"params": {
"name": "get_campaigns",
"arguments": {
"date_range": {
"start_date": "2024-12-01",
"end_date": "2024-12-31"
}
}
}
}
Get search terms with pagination
{
"method": "tools/call",
"params": {
"name": "get_search_terms",
"arguments": {
"date_range": {
"start_date": "2024-12-01",
"end_date": "2024-12-31"
},
"paging": {
"limit": 100,
"offset": 0
}
}
}
}
Docker Support
# Build the image
docker build -t mcp-google-ads .
# Run with environment variables
docker run -p 3000:3000 \
-e GOOGLE_ADS_CLIENT_ID=your_client_id \
-e GOOGLE_ADS_CLIENT_SECRET=your_client_secret \
-e GOOGLE_ADS_DEVELOPER_TOKEN=your_developer_token \
-e GOOGLE_ADS_REFRESH_TOKEN=your_refresh_token \
mcp-google-ads
Development
# Run in development mode with hot reload
npm run dev:server
# Run tests
npm test
# Lint and format
npm run lint
Troubleshooting
"Invalid Customer ID"
- Ensure the customer ID is 10 digits with no dashes
- Verify you have access to the account
"Permission Denied"
- Check that the OAuth user has access to the Google Ads account
- Ensure the developer token is approved for the account level
"Authentication Failed"
- Regenerate your refresh token
- Verify client ID and secret are correct
"Developer Token Not Approved"
- For production, apply for Basic or Standard access
- Test accounts don't count against quotas
"Write operations are disabled" / write tools missing
- Set
GOOGLE_ADS_ENABLE_WRITES=truein the server's environment - Restart the server and reconnect the MCP client — clients cache
tools/list
A dry run returned no resource names
- Expected.
validate_onlyrequests are not applied, so the API returns no resource names andapplied_countis 0. Re-run withdry_run: falseto apply.
"exceeds the configured GOOGLE_ADS_MAX_BUDGET_MICROS cap"
- The requested budget or bid is above your local ceiling. Raise or unset
GOOGLE_ADS_MAX_BUDGET_MICROS/GOOGLE_ADS_MAX_BID_MICROS. - Remember these are micros: 100 units/day is
100000000.
API Rate Limits
Google Ads API has rate limits:
- 10,000 requests per day (Basic)
- 15,000 requests per day (Standard)
The server includes automatic retry logic for transient errors.
Related Projects
- Model Context Protocol - The protocol specification
- MCP Servers - Official MCP servers
- Google Ads API - Google Ads API documentation
License
MIT
Contributing
Contributions are welcome! Please open an issue or submit a pull request.
Support
- Open an issue for bug reports or feature requests
- Star the repo if you find it useful!
