NG

ncosentino/google-keyword-planner-mcp

Developer tools
55 stars 15 forks Quality 78 Trend 27MIT

An 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:

  1. Access to the target Google Ads account – a manager account is needed only when accessing the target through a manager hierarchy.

  2. 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.

  3. 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 for GOOGLE_ADS_CUSTOMER_ID is a sub-account managed through your manager account, set GOOGLE_ADS_LOGIN_CUSTOMER_ID to 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 omit GOOGLE_ADS_LOGIN_CUSTOMER_ID.

  4. A Google Cloud project with the Google Ads API enabled.

  5. An OAuth2 client ID and secret from your GCP project (Desktop app type – see GCP setup below).

  6. 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.

  7. 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-7890 and 1234567890 both work.

2. GCP Setup

  1. Go to GCP Console → APIs & Services → Credentials
  2. Enable the Google Ads API: https://console.cloud.google.com/apis/library/googleads.googleapis.com
  3. 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
  4. Create an OAuth2 client: + Create Credentials → OAuth client ID → Desktop app → Create
  5. 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_ID is 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".

  • language and geo targets accept ISO codes, English names, bare IDs, and resource names. Regions and cities can be found with search_geo_targets.
  • limit (optional, default 50) – maximum number of ideas, highest search volume first across all API pages. Use -1 for all ideas. totalFound counts ideas before filtering and truncation.
  • min_monthly_searches / minMonthlySearches (optional) – drop ideas below this average monthly search volume.
  • include_monthly_volumes / includeMonthlyVolumes (optional, default false) – add available monthlySearchVolumes to each idea.
  • Each idea also carries competitionIndex, and the response echoes the language and geoTargetConstants it 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 keywords
  • maxCpcMicros (optional, default 1000000) – maximum CPC bid in micros (1,000,000 = $1.00)
  • forecastDays (optional, default 30) – 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.).



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:

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.

View the full README on GitHub

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" } } } }