
ncosentino/google-keyword-planner-mcp
Developer toolsAn MCP server for working with Google Keyword Planner
Overview
Pre-built native binaries for Linux, macOS, and Windows. No Node.js. No Python. No .NET runtime. No Go toolchain. Download one binary and configure your AI tool. Expose Google Ads Keyword Planner data directly to AI assistants like Claude, GitHub Copilot, and Cursor via the Model Context Protocol (MCP). Ask your AI to find keyword opportunities, analyze search volume trends, and forecast ad performance -- all grounded in real Google keyword data. AI assistants are powerful at SEO and content strategy -- but they need real keyword data.
README
Google Ads Keyword Planner MCP Server – Keyword Research for AI Assistants
Zero-dependency MCP server for Google Ads Keyword Planner. Pre-built native binaries for Linux, macOS, and Windows. No Node.js. No Python. No .NET runtime. No Go toolchain. Download one binary and configure your AI tool.
Expose Google Ads Keyword Planner data directly to AI assistants like Claude, GitHub Copilot, and Cursor via the Model Context Protocol (MCP). Ask your AI to find keyword opportunities, analyze search volume trends, and forecast ad performance – all grounded in real Google keyword data.
Why This Exists
AI assistants are powerful at SEO and content strategy – but they need real keyword data. This MCP server bridges your AI tool to the Google Ads Keyword Planner API, giving it:
- Keyword ideas – related keywords with average monthly searches, competition level, and CPC estimates from a seed keyword or URL
- Historical metrics – search volume trends, competition scores, and bid ranges for any list of keywords
- Forecasts – available projected clicks, cost, and average CPC at a given max CPC bid
With this MCP server configured, you can ask your AI: “What keywords should I target for a blog post about dependency injection in C#? What’s the search volume and competition look like?” and get a real data-backed answer.
Available Tools
| Tool | Description |
|---|---|
generate_keyword_ideas |
Generate related keywords from seed keywords and/or a URL with search volume and CPC data |
get_historical_metrics |
Get historical search volume, competition, and CPC for a list of keywords |
get_keyword_forecast |
Get available projected clicks, cost, and average CPC at a given max CPC bid |
search_geo_targets |
Find regions and cities by name and return their geo target resource names |
Quick Start
1. Prerequisites
You need:
-
Access to the target Google Ads account – a manager account is needed only when accessing the target through a manager hierarchy.
-
A Google Cloud project with production API access – apply through the Google Ads API overview.
Developer tokens were sunset on September 9, 2026. Project Test access cannot query production accounts; apply for Explorer or a higher access level. Legacy token flags remain optional for compatibility. See Google’s migration guide.
-
A Google Ads account with billing configured – the Keyword Planner API requires an account with an active payment method. You do not need to run any ads or spend money; you just need a payment method on file. This can be your manager account itself (if it has billing) or a separate sub-account.
Note on
GOOGLE_ADS_LOGIN_CUSTOMER_ID: If the account you use forGOOGLE_ADS_CUSTOMER_IDis a sub-account managed through your manager account, setGOOGLE_ADS_LOGIN_CUSTOMER_IDto your manager account ID. If you are calling the account directly (i.e., the OAuth user has direct access to it without going through a manager), you can omitGOOGLE_ADS_LOGIN_CUSTOMER_ID. -
A Google Cloud project with the Google Ads API enabled.
-
An OAuth2 client ID and secret from your GCP project (Desktop app type – see GCP setup below).
-
An OAuth2 refresh token obtained via a one-time authorization flow (see below). The OAuth2 flow must be completed as the Google account that has access to the Google Ads account you intend to use.
-
A Google Ads customer ID – the ID of the account with billing (found in the top-right corner of the Google Ads UI). Dashes are optional:
123-456-7890and1234567890both work.
2. GCP Setup
- Go to GCP Console → APIs & Services → Credentials
- Enable the Google Ads API:
https://console.cloud.google.com/apis/library/googleads.googleapis.com - Configure the OAuth consent screen (
https://console.cloud.google.com/apis/credentials/consent):- Select External → fill in app name and your email → Save
- Add your own Google account as a Test user
- Leave the app in Testing mode
- Create an OAuth2 client: + Create Credentials → OAuth client ID → Desktop app → Create
- Copy the Client ID and Client Secret
3. Obtain a Refresh Token
Run this one-time flow to get a refresh token. It starts a local HTTP listener to automatically capture the authorization code:
PowerShell (Windows):
$clientId = "YOUR_CLIENT_ID"
$clientSecret = "YOUR_CLIENT_SECRET"
$redirectUri = "http://localhost:9876"
$authUrl = "https://accounts.google.com/o/oauth2/v2/auth?client_id=$clientId&redirect_uri=$([Uri]::EscapeDataString($redirectUri))&response_type=code&scope=$([Uri]::EscapeDataString('https://www.googleapis.com/auth/adwords'))&access_type=offline&prompt=consent"
$listener = [System.Net.HttpListener]::new()
$listener.Prefixes.Add("$redirectUri/")
$listener.Start()
Start-Process $authUrl
$context = $listener.GetContext()
$rawUrl = $context.Request.RawUrl
$responseText = "Auth complete! You can close this tab."
$buffer = [System.Text.Encoding]::UTF8.GetBytes($responseText)
$context.Response.ContentLength64 = $buffer.Length
$context.Response.OutputStream.Write($buffer, 0, $buffer.Length)
$context.Response.Close()
$listener.Stop()
$code = ($rawUrl -split "[?&]" | Where-Object { $_ -like "code=*" }) -replace "^code=", ""
$body = "client_id=$clientId&client_secret=$clientSecret&code=$([Uri]::EscapeDataString($code))&grant_type=authorization_code&redirect_uri=$([Uri]::EscapeDataString($redirectUri))"
$result = Invoke-RestMethod -Method Post -Uri "https://oauth2.googleapis.com/token" -Body $body -ContentType "application/x-www-form-urlencoded"
Write-Host "Refresh token: $($result.refresh_token)"
bash (Linux/macOS):
CLIENT_ID="YOUR_CLIENT_ID"
CLIENT_SECRET="YOUR_CLIENT_SECRET"
REDIRECT_URI="http://localhost:9876"
# Open auth URL in browser
open "https://accounts.google.com/o/oauth2/v2/auth?client_id=${CLIENT_ID}&redirect_uri=${REDIRECT_URI}&response_type=code&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fadwords&access_type=offline&prompt=consent"
# Start a temporary HTTP server to catch the redirect
CODE=$(python3 -c "
import http.server, urllib.parse, sys
class H(http.server.BaseHTTPRequestHandler):
def do_GET(self):
params = urllib.parse.parse_qs(urllib.parse.urlparse(self.path).query)
print(params['code'][0], end='')
self.send_response(200); self.end_headers()
self.wfile.write(b'Auth complete! Close this tab.')
sys.exit(0)
def log_message(self, *a): pass
http.server.HTTPServer(('', 9876), H).handle_request()
")
# Exchange for tokens
curl -s -X POST https://oauth2.googleapis.com/token \
-d "client_id=${CLIENT_ID}&client_secret=${CLIENT_SECRET}&code=${CODE}&grant_type=authorization_code&redirect_uri=${REDIRECT_URI}" \
| python3 -c "import sys,json; print(json.load(sys.stdin)['refresh_token'])"
The refresh_token value printed is what you need. Save it – you cannot retrieve it again.
4. Download the Binary
Download the latest binary for your platform from the Releases page.
| Platform | Go binary | C# AOT binary |
|---|---|---|
| Linux x64 | kwp-mcp-go-linux-amd64 |
kwp-mcp-csharp-linux-x64 |
| Linux ARM64 | kwp-mcp-go-linux-arm64 |
kwp-mcp-csharp-linux-arm64 |
| macOS x64 | kwp-mcp-go-darwin-amd64 |
kwp-mcp-csharp-osx-x64 |
| macOS ARM64 | kwp-mcp-go-darwin-arm64 |
kwp-mcp-csharp-osx-arm64 |
| Windows x64 | kwp-mcp-go-windows-amd64.exe |
kwp-mcp-csharp-win-x64.exe |
| Windows ARM64 | kwp-mcp-go-windows-arm64.exe |
kwp-mcp-csharp-win-arm64.exe |
5. Configure Your MCP Client
GitHub Copilot CLI / Claude Code
{
"mcpServers": {
"keyword-planner": {
"type": "stdio",
"command": "/path/to/kwp-mcp-go-linux-amd64",
"args": [],
"env": {
"GOOGLE_ADS_DEVELOPER_TOKEN": "your-developer-token",
"GOOGLE_ADS_CLIENT_ID": "your-client-id.apps.googleusercontent.com",
"GOOGLE_ADS_CLIENT_SECRET": "your-client-secret",
"GOOGLE_ADS_REFRESH_TOKEN": "your-refresh-token",
"GOOGLE_ADS_CUSTOMER_ID": "your-sub-account-id",
"GOOGLE_ADS_LOGIN_CUSTOMER_ID": "your-manager-account-id"
}
}
}
}
Note: Some MCP clients (e.g. GitHub Copilot CLI) require
"args": []to be present when"type": "stdio"is specified. Claude Desktop does not require it. If your client fails to load the server, try adding"args": [].
Claude Desktop (claude_desktop_config.json)
{
"mcpServers": {
"keyword-planner": {
"command": "/path/to/kwp-mcp-go-linux-amd64",
"env": {
"GOOGLE_ADS_DEVELOPER_TOKEN": "your-developer-token",
"GOOGLE_ADS_CLIENT_ID": "your-client-id.apps.googleusercontent.com",
"GOOGLE_ADS_CLIENT_SECRET": "your-client-secret",
"GOOGLE_ADS_REFRESH_TOKEN": "your-refresh-token",
"GOOGLE_ADS_CUSTOMER_ID": "your-sub-account-id",
"GOOGLE_ADS_LOGIN_CUSTOMER_ID": "your-manager-account-id"
}
}
}
}
Important: Manager Account + Sub-Account Setup
The GOOGLE_ADS_LOGIN_CUSTOMER_ID credential is the manager/MCC account ID. It is required only when your GOOGLE_ADS_CUSTOMER_ID account is a sub-account managed through a manager account – it tells the API which manager to authenticate through by sending it as the login-customer-id HTTP header.
If the account you set as GOOGLE_ADS_CUSTOMER_ID is directly accessible by your OAuth credentials (i.e., you can see it in listAccessibleCustomers and it is not being accessed through a manager), you can omit GOOGLE_ADS_LOGIN_CUSTOMER_ID.
When in doubt: set it to your manager account ID. It doesn’t hurt to include it even when not strictly required.
Both IDs can be found in the Google Ads UI – the account number shown in the top-right corner when viewing that account. Dashes are optional – 123-456-7890 and 1234567890 both work.
Important: Billing Requirement
The Google Ads Keyword Planner API requires an account with billing configured. Accounts without an active payment method return errors regardless of credentials. You do not need to run any ads or spend money – you just need a payment method on file.
Common Errors
| Error | Meaning | Fix |
|---|---|---|
CLOUD_PROJECT_NOT_APPROVED_FOR_PRODUCTION |
The OAuth project has Test access only | Apply for production access in the Google Ads API overview in Cloud Console |
USER_PERMISSION_DENIED with mention of login-customer-id |
The account is a managed sub-account but GOOGLE_ADS_LOGIN_CUSTOMER_ID is missing or wrong |
Set GOOGLE_ADS_LOGIN_CUSTOMER_ID to your manager account ID |
USER_PERMISSION_DENIED without mention of login-customer-id |
The OAuth user doesn’t have access to the customer account | Complete the OAuth flow as the Google account that owns or has access to the ads account |
HTTP 400 INVALID_ARGUMENT |
Often a malformed customer ID or missing required field | Check that GOOGLE_ADS_CUSTOMER_ID contains only digits (dashes are stripped automatically) |
Configuration Reference
Credentials are resolved in this priority order: CLI flag > environment variable > .env file.
| Credential | CLI flag | Environment variable | Required | Description |
|---|---|---|---|---|
| Legacy developer token | --developer-token |
GOOGLE_ADS_DEVELOPER_TOKEN |
No | Optional compatibility setting; ignored by Google since September 9, 2026 |
| OAuth2 client ID | --client-id |
GOOGLE_ADS_CLIENT_ID |
Yes | From GCP OAuth2 credentials |
| OAuth2 client secret | --client-secret |
GOOGLE_ADS_CLIENT_SECRET |
Yes | From GCP OAuth2 credentials |
| Refresh token | --refresh-token |
GOOGLE_ADS_REFRESH_TOKEN |
Yes | From one-time OAuth2 flow |
| Customer ID | --customer-id |
GOOGLE_ADS_CUSTOMER_ID |
Yes | Sub-account ID with billing (not the manager account) |
| Login customer ID | --login-customer-id |
GOOGLE_ADS_LOGIN_CUSTOMER_ID |
Yes* | Manager/MCC account ID. Required when using a sub-account. |
| Default language | – | GOOGLE_ADS_DEFAULT_LANGUAGE |
No | Language used when a tool call sets none, as an ISO code (de) or resource name (languageConstants/1001). |
| Default geo targets | – | GOOGLE_ADS_DEFAULT_GEO_TARGETS |
No | Comma-separated geo targets used when a tool call sets none, as ISO country codes (AT) or resource names (geoTargetConstants/2040). |
* Required when
GOOGLE_ADS_CUSTOMER_IDis a managed sub-account (accessed through a manager/MCC account). Can be omitted if your OAuth credentials have direct access to the customer account.
.env File
Place a .env file in the same directory as the binary:
GOOGLE_ADS_DEVELOPER_TOKEN=your-developer-token
GOOGLE_ADS_CLIENT_ID=your-client-id.apps.googleusercontent.com
GOOGLE_ADS_CLIENT_SECRET=your-client-secret
GOOGLE_ADS_REFRESH_TOKEN=your-refresh-token
GOOGLE_ADS_CUSTOMER_ID=your-sub-account-id
GOOGLE_ADS_LOGIN_CUSTOMER_ID=your-manager-account-id
Transports
Both binaries default to stdio and also support one shared Streamable HTTP service for every agent session:
./kwp-mcp-go-linux-amd64 \
--transport http \
--listen-address 127.0.0.1 \
--port 8082
Keep the Google Ads credentials in the service environment or a .env file
beside the binary. MCP is served at /mcp; supervisors can probe /health.
{
"mcpServers": {
"keyword-planner": {
"type": "http",
"url": "http://127.0.0.1:8082/mcp",
"tools": ["*"]
}
}
}
Both implementations default to loopback, validate Host and Origin boundaries, limit request sizes and HTTP timeouts, and run statelessly without session affinity.
The HTTP host does not authenticate ordinary MCP callers. Keep it on loopback or place it behind TLS and an authenticated reverse proxy. See Shared Service and Transports.
Tool Reference
generate_keyword_ideas
Generates related keyword ideas from seed keywords and/or a URL.
Parameters:
seedKeywords(optional) – comma-separated seed keywords (e.g."C# tutorial, dotnet performance")url(optional) – a URL to generate keyword ideas from (e.g."https://devleader.ca")language(optional) – language resource name (e.g."languageConstants/1000"for English)
At least one of seedKeywords or url must be provided.
Returns: List of keyword ideas with avgMonthlySearches, competition (LOW/MEDIUM/HIGH), lowTopOfPageBidMicros, highTopOfPageBidMicros.
Note: Without active ad spend, search volumes are shown as ranges. Any minimal ad spend unlocks precise monthly volumes.
Both servers: Go uses snake_case names and arrays; C# uses camelCase names
and comma-separated strings. For example, Go’s seed_keywords: ["go", "csharp"]
corresponds to C#'s seedKeywords: "go, csharp".
languageand geo targets accept ISO codes, English names, bare IDs, and resource names. Regions and cities can be found withsearch_geo_targets.limit(optional, default50) – maximum number of ideas, highest search volume first across all API pages. Use-1for all ideas.totalFoundcounts ideas before filtering and truncation.min_monthly_searches/minMonthlySearches(optional) – drop ideas below this average monthly search volume.include_monthly_volumes/includeMonthlyVolumes(optional, defaultfalse) – add availablemonthlySearchVolumesto each idea.- Each idea also carries
competitionIndex, and the response echoes thelanguageandgeoTargetConstantsit used.
get_historical_metrics
Gets historical search volume and competition data for a specific list of keywords.
Parameters:
keywords(required) – comma-separated list of keywords (e.g."dependency injection, SOLID principles")
Returns: Per-keyword metrics including avgMonthlySearches, competition, competitionIndex, bid estimates, and monthlySearchVolumes (12-month breakdown).
get_keyword_forecast
Projects available clicks, cost, and average CPC at a specified maximum CPC bid.
Parameters:
keywords(required) – comma-separated list of keywordsmaxCpcMicros(optional, default1000000) – maximum CPC bid in micros (1,000,000 = $1.00)forecastDays(optional, default30) – number of days to forecast
Both implementations, v25: Return total for all keywords in one ad group, keywords for
optional isolated scenarios, the inclusive startDate/endDate, matchType,
and effective targeting. Available metrics are clicks, costMicros, and
averageCpcMicros; unavailable values are omitted. Impressions, CTR, and
manual-CPC conversion predictions are not available.
Both accept language, geography, match type, currency, and optional breakdown.
Go names are geo_target_constants, match_type, currency_code, and
include_keyword_breakdown; C# names are geoTargetConstants, matchType,
currencyCode, and includeKeywordBreakdown. Breakdown defaults to false. The
breakdown uses an extra request per keyword; these independent scenarios must
not be summed to obtain the combined total. Planning calls share a per-client
one-request-per-second gate for the configured customer. This gate is local to
one client/process, not shared between independently running servers; multiple
instances using the same customer must coordinate their aggregate quota.
Currency values are
in micros of the requested currency, or the account currency when omitted.
For all tools, omitted targeting uses the configured defaults. In Go, pass
language: "all" and geo_target_constants: [] to override defaults with
unrestricted targeting. In C#, use language: "all" and geoTargetConstants: "".
Whitespace-only language or blank geo entries are errors.
search_geo_targets
Finds locations by name so they can be passed as geo targets.
Parameters:
names(required) – location names (Go:["Wien", "Steiermark"]; C#:"Wien, Steiermark")country_code/countryCode(optional) – ISO country code restricting the search (e.g."AT")locale(optional, default"en") – language of the returned names
Returns: Matching locations with resourceName, name, canonicalName, countryCode, targetType and reach.
Go vs C# – Which Binary to Use?
Both implementations use repaired Google Ads v25 historical and forecast contracts, configurable targeting, enriched ideas, location discovery, and bounded optional forecast concurrency. The integration and parity evidence is tracked in #32. Go uses snake_case argument names and keyword arrays; C# retains camelCase names and comma-separated keyword strings. Existing input formats are intentionally preserved, so callers must use the argument names and formats for their binary.
| Go | C# AOT | |
|---|---|---|
| Binary size | ~12 MB | ~20 MB |
| Startup time | < 5ms | ~20ms |
| Memory usage | Lower | Slightly higher |
| Platform | All | All |
| Transports | stdio, HTTP | stdio, HTTP |
For most MCP use cases, either works fine. The Go binary starts slightly faster; the C# binary may be preferred if you’re already in a .NET ecosystem. Both support the same HTTP transport with equivalent security defaults.
Building from Source
Go
cd go
go build -o kwp-mcp-go .
C#
cd csharp
dotnet publish src/KeywordPlannerMcp/KeywordPlannerMcp.csproj -r linux-x64 -c Release --self-contained true
Replace linux-x64 with your target RID (win-x64, osx-arm64, etc.).
Related Projects
- google-psi-mcp – Google PageSpeed Insights MCP server
- google-search-console-mcp – Google Search Console MCP server
About
Nick Cosentino – Dev Leader
This MCP server was built by Nick Cosentino, a software engineer and content creator known as Dev Leader. Nick creates practical .NET, C#, ASP.NET Core, Blazor, and software engineering content for intermediate to advanced developers – covering everything from performance optimization and clean architecture to real-world career advice.
This tool was born out of real work researching keywords and planning SEO strategy for devleader.ca and the desire to use AI assistants effectively during that process. It serves as a practical example of building Native AOT C# and idiomatic Go MCP servers with zero runtime dependencies.
Find Nick online:
- Blog: https://www.devleader.ca
- YouTube: https://www.youtube.com/@devleaderca
- Newsletter: https://weekly.devleader.ca
- LinkedIn: https://linkedin.com/in/nickcosentino
- All My Links: https://links.devleader.ca
BrandGhost
BrandGhost is a social media automation platform built by Nick that lets content creators cross-post and schedule content across all social platforms in one click. If you create content and want to spend less time on distribution and more time creating, check it out.
Contributing
Issues and pull requests welcome. Please run go test ./... and dotnet test before submitting.
License
MIT License – see LICENSE.
Install
This server does not publish a one-line install command.
Open the repository installation guide New to MCP? Start with the mcpskill.org guide →Configuration
{
"mcpServers": {
"keyword-planner": {
"type": "stdio",
"command": "/path/to/kwp-mcp-go-linux-amd64",
"args": [],
"env": {
"GOOGLE_ADS_DEVELOPER_TOKEN": "your-developer-token",
"GOOGLE_ADS_CLIENT_ID": "your-client-id.apps.googleusercontent.com",
"GOOGLE_ADS_CLIENT_SECRET": "your-client-secret",
"GOOGLE_ADS_REFRESH_TOKEN": "your-refresh-token",
"GOOGLE_ADS_CUSTOMER_ID": "your-sub-account-id",
"GOOGLE_ADS_LOGIN_CUSTOMER_ID": "your-manager-account-id"
}
}
}
}