Skip to main content
Glama
ceotind

Google Ads MCP Reader

by ceotind
README.md
# Open Google MCP

A read-only [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) server for **Google Ads** (via GAQL), **Search Console**, and **Google Analytics 4**. Designed for AI assistants and LLM agents to safely read campaign performance, search analytics, website traffic, and more.

<img width="1672" height="941" alt="open-google-mcp" src="https://github.com/user-attachments/assets/3e082501-6a06-43bb-b26d-3f026ba76348" />

## Tools

### Google Ads

| Tool | Input | Description |
|------|-------|-------------|
| `list_accessible_customers` | — | Discover Google Ads account IDs |
| `get_resource_metadata` | `resource_name` | Explore selectable/filterable/sortable fields for a resource |
| `search` | `customer_id`, `resource`, `fields`, `conditions?`, `orderings?`, `limit?` | Execute read-only GAQL queries |

### Google Search Console

| Tool | Input | Description |
|------|-------|-------------|
| `gsc_get_capabilities` | — | Get categorized tool list with auth status |
| `gsc_list_properties` | — | List all GSC properties (start here) |
| `gsc_get_site_details` | `site_url` | Verification/ownership for a property |
| `gsc_get_search_analytics` | `site_url`, `days?`, `dimensions?`, `row_limit?` | Top queries with clicks, impressions, CTR, position |
| `gsc_get_performance_overview` | `site_url`, `days?` | Executive summary with daily trend |
| `gsc_compare_search_periods` | `site_url`, `period1_start/end`, `period2_start/end` | Compare two date ranges |
| `gsc_get_advanced_search_analytics` | `site_url`, `start_date?`, `end_date?`, `dimensions?`, `search_type?`, `row_limit?`, `start_row?`, `sort_by?`, `sort_direction?`, `filters?` | Filtered, sorted, paginated analytics |
| `gsc_inspect_url` | `site_url`, `page_url` | Debug a URL's crawl/index status |
| `gsc_get_sitemaps` | `site_url` | List sitemaps with status and errors |

### Google Analytics 4

| Tool | Input | Description |
|------|-------|-------------|
| `ga4_get_capabilities` | — | Get categorized tool list with auth status |
| `ga4_list_properties` | — | Discover all accessible GA4 properties |
| `ga4_get_metadata` | `property_id` | Browse available dimensions and metrics |
| `ga4_run_report` | `property_id`, `dimensions?`, `metrics?`, `start_date?`, `end_date?`, `filters?`, `order_bys?`, `limit?` | Run a standard report with filtering and ordering |
| `ga4_run_realtime_report` | `property_id`, `dimensions?`, `metrics?`, `limit?` | Get real-time data (last 30 minutes) |

### Google Keyword Planner

| Tool | Input | Description |
|------|-------|-------------|
| `generate_keyword_ideas` | `customer_id?`, `keywords?`/`url?`, `geos?`, `language?`, `network?`, `page_size?` | Keyword ideas with search volume, competition, bid ranges |
| `generate_keyword_historical_metrics` | `customer_id?`, `keywords`, `geos?`, `language?` | Historical metrics for exact keywords |
| `generate_keyword_forecast_metrics` | `customer_id?`, `keywords`, `match_types?`, `currency_code?`, `start/end_date?`, `budget/bid?` | Simulated campaign forecast (impressions, clicks, CPC, cost) |
| `suggest_geo_target_constants` | `names`, `country_code?`, `locale?` | Look up geo resource names (e.g. Ireland → `geoTargetConstants/2372`) |

Keyword Planner tools reuse the same service account + developer token as the Ads tools. `customer_id` is optional — it falls back to `GOOGLE_ADS_DEFAULT_CUSTOMER_ID`, then `GOOGLE_ADS_LOGIN_CUSTOMER_ID` from `.env`. The account needs Keyword Planner access (Tools → Planning → Keyword Planner in Google Ads); manager (MCC) accounts usually don't have it — use a child customer ID. **The developer token must have at least basic access** (explorer-level tokens are rejected: "apply for basic or standard access" — ads.google.com → Tools → API Center → Request higher access).

### Manager Account Auto-Routing

If `search` targets a **manager (MCC) account** and finds no data directly, it automatically:
1. Detects the account is a manager
2. Finds child accounts
3. Runs the query on each child
4. Returns combined results with `_source_customer_id` marking which account each row belongs to

## Setup

### Prerequisites (all paths)

- A Google Ads developer token (apply via Google Ads API Center)
- Google Cloud service account with the following APIs enabled:
  - [Google Ads API](https://console.cloud.google.com/apis/library/googleads.googleapis.com)
  - [Search Console API](https://console.cloud.google.com/apis/library/searchconsole.googleapis.com)
  - [Google Analytics Data API](https://console.cloud.google.com/apis/library/analyticsdata.googleapis.com)
  - [Google Analytics Admin API](https://console.cloud.google.com/apis/library/analyticsadmin.googleapis.com)

---

### Option A: Docker (recommended — no Python required)

#### 1. Clone

```bash
git clone https://github.com/ceotind/open-google-mcp.git
cd open-google-mcp
```

#### 2. Create and authorize a service account

A single service account authenticates Google Ads, Search Console, and Google Analytics 4.

1. Go to **GCP Console → APIs & Services → Credentials → Create Credentials → Service Account**
2. Download the JSON key file, then copy these fields into `.env`:
   - `GOOGLE_SERVICE_ACCOUNT_EMAIL`
   - `GOOGLE_SERVICE_ACCOUNT_KEY` (the `private_key` — keep the `\n` escapes)
   - `GOOGLE_SERVICE_ACCOUNT_KEY_ID`
   - `GOOGLE_SERVICE_ACCOUNT_CLIENT_ID`
   - `GOOGLE_SERVICE_ACCOUNT_PROJECT_ID`
3. **Google Ads**: add the service account email in your manager account
   (**Settings → Account access → Service account users**)
4. **Search Console**: add the same email to your property
   (**Settings → Users and permissions → Add user**, grant Full access)
5. **Google Analytics 4**: add the same email to your GA4 property
   (**Admin → Account Access Management → Add user → Viewer**)

#### 3. Configure `.env`

```bash
cp .env.example .env
# Edit .env with the service account values + developer token
```

| Variable | Description |
|----------|-------------|
| `GOOGLE_ADS_DEVELOPER_TOKEN` | Your Google Ads API developer token |
| `GOOGLE_SERVICE_ACCOUNT_EMAIL` | Service account email |
| `GOOGLE_SERVICE_ACCOUNT_KEY` | Private key (with `\n` newlines) |
| `GOOGLE_SERVICE_ACCOUNT_KEY_ID` | Private key ID |
| `GOOGLE_SERVICE_ACCOUNT_CLIENT_ID` | Client ID |
| `GOOGLE_SERVICE_ACCOUNT_PROJECT_ID` | GCP project ID |
| `GOOGLE_ADS_LOGIN_CUSTOMER_ID` | Optional — for MCC account routing |
| `GOOGLE_ADS_DEFAULT_CUSTOMER_ID` | Optional — default customer_id for Keyword Planner tools (falls back to LOGIN_CUSTOMER_ID) |
| `GA4_CREDENTIALS_PATH` | Optional — dedicated GA4 credential file |

#### 4. Build and start the container (once)

```bash
docker compose build
docker compose up -d open-google-mcp
```

> This starts a single long-lived container. Client configs attach to it with
> `docker compose exec` instead of `docker compose run`, so a new container is
> **not** created every session. (Don't use `docker compose run` for a client —
> it spawns a new container each launch and `--rm` never removes a long-running
> stdio server, so containers pile up.)

#### 5. MCP client config

**Claude Desktop:**
```json
{
  "mcpServers": {
    "open-google-mcp": {
      "command": "docker",
      "args": ["compose", "exec", "-T", "open-google-mcp", "python", "-m", "open_google_mcp"],
      "workdir": "/path/to/open-google-mcp"
    }
  }
}
```

**opencode:**
```json
{
  "mcp": {
    "open-google-mcp": {
      "type": "local",
      "command": ["docker", "compose", "exec", "-T", "open-google-mcp", "python", "-m", "open_google_mcp"],
      "workdir": "/path/to/open-google-mcp",
      "enabled": true
    }
  }
}
```

**Cursor:**
- **Name**: `open-google-mcp`
- **Type**: `command`
- **Command**: `docker compose exec -T open-google-mcp python -m open_google_mcp`
- **Working directory**: `/path/to/open-google-mcp`

---

### Option B: Python (local install)

#### 1. Clone and install

```bash
git clone https://github.com/ceotind/open-google-mcp.git
cd open-google-mcp
python3 -m venv .venv
source .venv/bin/activate
pip install -e .
```

#### 2. Create and authorize a service account

Same steps as Option A above — a single service account works for Ads, GSC, and GA4.

1. Go to **GCP Console → APIs & Services → Credentials → Create Credentials → Service Account**
2. Download the JSON key file, then copy these fields into `.env`:
   - `GOOGLE_SERVICE_ACCOUNT_EMAIL`
   - `GOOGLE_SERVICE_ACCOUNT_KEY`
   - `GOOGLE_SERVICE_ACCOUNT_KEY_ID`
   - `GOOGLE_SERVICE_ACCOUNT_CLIENT_ID`
   - `GOOGLE_SERVICE_ACCOUNT_PROJECT_ID`
3. **Google Ads**: add the service account email in your manager account
4. **Search Console**: add the same email to your property with Full access
5. **Google Analytics 4**: add the same email to your GA4 property with Viewer access

#### 3. Configure `.env`

```bash
cp .env.example .env
```

| Variable | Description |
|----------|-------------|
| `GOOGLE_ADS_DEVELOPER_TOKEN` | Your Google Ads API developer token |
| `GOOGLE_SERVICE_ACCOUNT_EMAIL` | Service account email |
| `GOOGLE_SERVICE_ACCOUNT_KEY` | Private key (with `\n` newlines) |
| `GOOGLE_SERVICE_ACCOUNT_KEY_ID` | Private key ID |
| `GOOGLE_SERVICE_ACCOUNT_CLIENT_ID` | Client ID |
| `GOOGLE_SERVICE_ACCOUNT_PROJECT_ID` | GCP project ID |
| `GOOGLE_ADS_LOGIN_CUSTOMER_ID` | Optional — for MCC account routing |
| `GOOGLE_ADS_DEFAULT_CUSTOMER_ID` | Optional — default customer_id for Keyword Planner tools (falls back to LOGIN_CUSTOMER_ID) |
| `GA4_CREDENTIALS_PATH` | Optional — dedicated GA4 credential file |

#### 4. Run

```bash
source .venv/bin/activate
source .env
python -m open_google_mcp
```

Or use the wrapper script (loads `.env` automatically):
```bash
./server.sh
```

#### 5. MCP client config

**Claude Desktop:**
```json
{
  "mcpServers": {
    "open-google-mcp": {
      "command": "/path/to/open-google-mcp/server.sh"
    }
  }
}
```

**opencode:**
```json
{
  "mcp": {
    "open-google-mcp": {
      "type": "local",
      "command": ["/path/to/open-google-mcp/server.sh"],
      "enabled": true
    }
  }
}
```

**Cursor:**
- **Name**: `open-google-mcp`
- **Type**: `command`
- **Command**: `/path/to/open-google-mcp/server.sh`

---

## Google Ads — Usage Examples

### List Ads accounts

```
list_accessible_customers
```

### Discover fields for a resource

```
get_resource_metadata(resource_name="campaign")
```

### Get active search campaigns with performance

```
search(
  customer_id="1234567890",
  resource="campaign",
  fields=["campaign.id", "campaign.name", "campaign.status",
          "campaign.advertising_channel_type", "metrics.clicks",
          "metrics.impressions", "metrics.cost_micros"],
  conditions=["campaign.status = 'ENABLED'",
              "segments.date DURING LAST_30_DAYS"],
  orderings=["metrics.clicks DESC"],
  limit=10
)
```

### Get child accounts under a manager

```
search(
  customer_id="1234567890",
  resource="customer_client",
  fields=["customer_client.id", "customer_client.descriptive_name", "customer_client.manager"]
)
```

---

## Google Search Console — Usage Examples

### List GSC properties

```
gsc_list_properties
```

### Get search analytics

```
gsc_get_search_analytics(
  site_url="https://example.com",
  days=28,
  dimensions="query",
  row_limit=10
)
```

### Get performance overview

```
gsc_get_performance_overview(
  site_url="https://example.com",
  days=28
)
```

### Compare two time periods

```
gsc_compare_search_periods(
  site_url="https://example.com",
  period1_start="2025-01-01",
  period1_end="2025-01-31",
  period2_start="2025-02-01",
  period2_end="2025-02-28",
  dimensions="query",
  limit=10
)
```

### Advanced analytics with filters

```
gsc_get_advanced_search_analytics(
  site_url="https://example.com",
  dimensions="query,device",
  search_type="WEB",
  row_limit=50,
  sort_by="impressions",
  sort_direction="descending",
  filters='[{"dimension":"country","operator":"equals","expression":"usa"}]'
)
```

### Inspect a URL

```
gsc_inspect_url(
  site_url="https://example.com",
  page_url="https://example.com/blog/post"
)
```

### List sitemaps

```
gsc_get_sitemaps(site_url="https://example.com")
```

---

## Google Analytics 4 — Usage Examples

### Start here — discover properties

```
ga4_list_properties
```

Returns your property ID (e.g. `123456789`) — use it in all other GA4 tools.

### Browse available dimensions and metrics

```
ga4_get_metadata(property_id="123456789")
```

Returns 300+ dimensions and 100+ metrics available for reporting.

### Run a basic report (property-level summary)

```
ga4_run_report(
  property_id="123456789",
  metrics="activeUsers,sessions,newUsers",
  start_date="28daysAgo",
  end_date="today"
)
```

### Top countries by users

```
ga4_run_report(
  property_id="123456789",
  dimensions="country",
  metrics="activeUsers",
  start_date="28daysAgo",
  end_date="today",
  limit=10,
  order_bys=[{"metric": "activeUsers", "desc": true}]
)
```

### Top pages with views

```
ga4_run_report(
  property_id="123456789",
  dimensions="pagePath",
  metrics="screenPageViews,activeUsers",
  start_date="28daysAgo",
  end_date="today",
  limit=10,
  order_bys=[{"metric": "screenPageViews", "desc": true}]
)
```

### Traffic sources breakdown

```
ga4_run_report(
  property_id="123456789",
  dimensions="sessionDefaultChannelGrouping",
  metrics="activeUsers,sessions",
  start_date="28daysAgo",
  end_date="today",
  limit=10,
  order_bys=[{"metric": "activeUsers", "desc": true}]
)
```

### Filter by country (e.g. only Ireland)

```
ga4_run_report(
  property_id="123456789",
  dimensions="city",
  metrics="activeUsers",
  start_date="28daysAgo",
  end_date="today",
  filters={"field": "country", "type": "string_filter",
           "value": "Ireland", "match_type": "EXACT"},
  limit=10
)
```

### Device breakdown

```
ga4_run_report(
  property_id="123456789",
  dimensions="deviceCategory",
  metrics="activeUsers,sessions",
  start_date="28daysAgo",
  end_date="today"
)
```

### Real-time report (last 30 minutes)

```
ga4_run_realtime_report(
  property_id="123456789",
  dimensions="country,deviceCategory",
  metrics="activeUsers",
  limit=10
)
```

### Date formats

You can use relative or absolute dates:
- Relative: `today`, `yesterday`, `7daysAgo`, `28daysAgo`, `30daysAgo`
- Absolute: `2026-01-01` (YYYY-MM-DD format)

---

## Google Keyword Planner — Usage Examples

`customer_id` is optional everywhere — set `GOOGLE_ADS_DEFAULT_CUSTOMER_ID` in `.env` and omit it in calls.

### Keyword ideas for a seed term

```text
generate_keyword_ideas(
  keywords=["salsa classes dublin", "salsa dancing"],
  geos=["geoTargetConstants/2372"],   # Ireland — find via suggest_geo_target_constants
  language="english"
)
```

### Historical metrics for exact keywords

```text
generate_keyword_historical_metrics(
  keywords=["salsa classes dublin", "bachata classes dublin"],
  geos=["geoTargetConstants/2372"]
)
```

### Forecast a campaign for next month

```text
generate_keyword_forecast_metrics(
  keywords=["salsa classes dublin", "salsa dancing"],
  match_types=["PHRASE", "EXACT"],
  currency_code="EUR",
  daily_budget_micros=20000000,   # €20/day
  max_cpc_bid_micros=500000       # €0.50 max CPC
)
```

### Find a geo constant

```text
suggest_geo_target_constants(names=["Ireland"], country_code="IE")
```

---

## GAQL Tips

- Field names must be fully qualified: `campaign.id`, not `id`
- Date format: `YYYY-MM-DD` with dashes
- Relative dates: `DURING LAST_7_DAYS`, `LAST_30_DAYS`, `THIS_MONTH`, `THIS_QUARTER`
- For `change_event` resource, limit must be ≤ 10000
- Conditions are AND-combined
- Use `get_resource_metadata` first to discover valid fields

## Development

```bash
python3 -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
pytest          # unit tests — no network or credentials required
```

The Keyword Planner logic lives in `src/open_google_mcp/keyword_planner.py` (pure request builders and result flatteners, fully unit-tested). A live smoke test against the real API is available:

```bash
source .env && python scripts/smoke_keyword_planner.py [customer_id]
```

## License

MIT

TDQS

A4.2/5.0

Scored across 6 tools

Disambiguation5/5

Each tool has a clearly distinct purpose: resource metadata exploration, GAQL search, customer ID listing, and three separate GSC functions (period comparison, performance overview, sitemap listing). The 'gsc_' prefix further distinguishes the Google Search Console tools from the Ads tools.

Naming Consistency3/5

Naming conventions are mixed: some tools use 'get_', one uses 'list_', one is bare 'search', and three use the 'gsc_' prefix but with different verb forms ('compare', 'get'). While snake_case is consistent, the lack of a uniform verb pattern makes the naming somewhat inconsistent.

Tool Count5/5

With 6 tools covering both Google Ads essentials (customer discovery, metadata, querying) and Google Search Console basics (overview, comparison, sitemaps), the count feels well-scoped for the combined domain.

Completeness2/5

The GSC tools reference a prerequisite 'gsc_list_properties' tool that does not exist, creating a dead end. For Google Ads, the 'search' tool is powerful but lacks higher-level convenience operations (e.g., getting campaign performance by ID). Overall, the surface has notable gaps.

Maintenance

ActivityMaintained
ResponsivenessNo issues