Grenada Google Ads MCP

Grenada Google Ads MCP

Enables authorized users to securely connect to and manage their Google Ads accounts through MCP clients, with support for campaigns, ad groups, ads, keywords, reporting, and billing. Runs as a remote multi-user server on Cloudflare Workers with per-user authentication and safety controls.

Category
Visit Server

README

Grenada Google Ads MCP

Multi-user remote Model Context Protocol connector for Google Ads, running on Cloudflare Workers.

  • Owner: Mostafa Ghanem — مصطفى غانم
  • Organization: Grenada Studio
  • Support: m.ghanem@grenadastudio.com
  • Runtime: Cloudflare Workers · D1 · KV
  • Transport: Remote MCP over Streamable HTTP at POST /mcp
  • Google Ads: REST API, version pinned by a single environment variable

What it does

Any authorised user signs in with their own Google account, and every Google Ads account they can reach becomes available to their MCP client (Claude, ChatGPT, Codex, MCP Inspector…).

Guarantees baked into the server, not left to the model:

Rule Where it is enforced
The user is never asked for an MCC / login_customer_id src/google-ads/account-discovery.ts, account-resolver.ts
A user can only touch accounts discovered for their user id resolveAccountContext — ownership check on every call
Manager accounts can never return metrics account-resolver.ts + gaql.ts
Campaigns/ad groups/ads/keywords are created PAUSED every create_* tool
A daily budget is mandatory, no silent default validateDailyBudgetMicros
Enabling, budget raises and deletions need an explicit one-time confirmation src/mcp/confirmations.ts
Read-only GAQL, always bounded by a LIMIT src/google-ads/gaql.ts
Per-user rate limits (60 read / 10 write / 20 GAQL / 2 mutate per min) src/mcp/permissions.ts
Refresh tokens AES-GCM encrypted, never logged src/crypto/aes-gcm.ts, src/db/audit.ts
Every write recorded in an audit log src/db/audit.ts

Tools — 62 implemented

29 read-only · 25 medium-risk · 8 high-risk (confirmation always required).

Area Tools
Accounts (4) list_google_ads_customers get_google_ads_account_info refresh_google_ads_accounts send_google_ads_invitation¹
Campaigns (9) get_google_ads_campaigns get_google_ads_campaign_metrics create_google_ads_campaign update_google_ads_campaign pause_google_ads_campaign enable_google_ads_campaign¹ set_google_ads_geo_targeting set_google_ads_language_targeting update_google_ads_network_settings
Ad groups (4) get_google_ads_ad_groups create_google_ads_ad_group get_google_ads_ad_group_metrics update_google_ads_ad_group
Ads (6) get_google_ads_ads create_google_ads_responsive_search_ad get_google_ads_ad_metrics pause_google_ads_ad enable_google_ads_ad¹ update_google_ads_video_ad_text
Keywords (11) get_google_ads_keywords add_google_ads_keywords add_google_ads_negative_keywords get_google_ads_negative_keywords get_google_ads_keyword_metrics get_google_ads_keyword_ideas pause_google_ads_keyword enable_google_ads_keyword¹ update_google_ads_keyword_bid remove_google_ads_keywords¹ remove_google_ads_negative_keywords¹
Reports (6) get_google_ads_search_terms_report get_google_ads_geo_performance get_google_ads_device_performance get_google_ads_hour_of_day_performance get_google_ads_auction_insights get_google_ads_bidding_strategy_report
Assets (3) list_google_ads_assets upload_google_ads_asset create_google_ads_image_assets_from_urls
Audiences (3) get_google_ads_audiences create_google_ads_custom_audience add_google_ads_audience_to_campaign
Billing (2) list_google_ads_invoices download_google_ads_invoice_pdf
Extensions (6) create_google_ads_sitelink create_google_ads_callout create_google_ads_structured_snippet get_google_ads_extensions update_google_ads_extension_status remove_google_ads_extension¹
Performance Max & Shopping (4) create_google_ads_pmax_campaign get_google_ads_pmax_asset_groups update_google_ads_pmax_asset_group create_google_ads_shopping_listing_group_tree
Advanced (4) execute_google_ads_gaql_query execute_google_ads_mutate¹ query_google_ads_api_docs submit_feedback

¹ high risk — answers CONFIRMATION_REQUIRED with a one-time token before doing anything.

Deviations from the reference catalog

Two of the 62 names in TOOL_CATALOG.md describe features of the reference product, not of the Google Ads API, so they were rebuilt natively instead of copied:

Reference tool What this project ships Why
create_creatives_from_dropbox_folder create_google_ads_image_assets_from_urls Bulk-uploads images from any public URL (including Dropbox direct links) with per-URL error reporting, instead of binding the connector to one storage vendor.
manage_account_slots refresh_google_ads_accounts "Account slots" are a billing-plan concept of the reference service. This connector has no slots: every discovered account is usable, so the equivalent action is re-running discovery.

Two more are worth knowing about because the API constrains them:

  • get_google_ads_auction_insights returns your own impression-share and top/absolute-top position metrics. The Google Ads API does not expose the competitor rows shown in the Auction Insights UI report — no implementation can return those.
  • query_google_ads_api_docs answers from a curated GAQL reference compiled into the Worker (src/google-ads/api-docs.ts), so it needs no third-party documentation service and works before a Google account is even connected.

Placeholder credentials

The repository ships with placeholders only — there is not a single real secret in it.

.dev.vars.example lists the five values you replace before going live:

GOOGLE_OAUTH_CLIENT_ID          PLACEHOLDER_GOOGLE_OAUTH_CLIENT_ID.apps.googleusercontent.com
GOOGLE_OAUTH_CLIENT_SECRET      PLACEHOLDER_GOOGLE_OAUTH_CLIENT_SECRET
GOOGLE_ADS_DEVELOPER_TOKEN      PLACEHOLDER_DEVELOPER_TOKEN
TOKEN_ENCRYPTION_KEY            PLACEHOLDER_TOKEN_ENCRYPTION_KEY_CHANGE_ME
MCP_COOKIE_ENCRYPTION_KEY       PLACEHOLDER_COOKIE_ENCRYPTION_KEY_CHANGE_ME

The whole app boots, serves every page and passes every test with these placeholders in place; only the calls that actually reach Google will fail until they are replaced.

Encryption keys: any string works (it is stretched with SHA-256), but generate real ones for production with openssl rand -base64 32.


Local development

npm install
cp .dev.vars.example .dev.vars
npx wrangler d1 migrations apply DB --local
npm run dev

Then check http://127.0.0.1:8787/health, /, /privacy, /terms, /data-deletion.

Run the checks:

npm run typecheck && npm test

Deployment

The short way

Authenticate once, in an interactive terminal:

npx wrangler login

Then run the deploy script — it creates the D1 database and both KV namespaces, writes their ids into wrangler.jsonc, generates and stores the encryption keys, pushes every secret, applies the migrations, deploys, and prints the live URL plus the exact redirect URI to register with Google:

pwsh ./deploy.ps1

Once you have the real Google credentials, re-run it with them:

pwsh ./deploy.ps1 -GoogleClientId "...apps.googleusercontent.com" -GoogleClientSecret "GOCSPX-..." -DeveloperToken "..."

The script is safe to re-run: existing resources are reused and the encryption keys are generated only once, into the git-ignored .deploy-keys.local.txt. Back that file up — losing TOKEN_ENCRYPTION_KEY makes every stored Google refresh token unreadable and forces all users to reconnect.

The manual way

1. Cloudflare resources

npx wrangler login
npx wrangler d1 create grenada_google_ads_mcp
npx wrangler kv namespace create OAUTH_KV
npx wrangler kv namespace create CACHE_KV

Copy the printed ids into wrangler.jsonc, replacing REPLACE_WITH_D1_DATABASE_ID, REPLACE_WITH_OAUTH_KV_ID and REPLACE_WITH_CACHE_KV_ID.

2. Secrets

npx wrangler secret put GOOGLE_OAUTH_CLIENT_ID
npx wrangler secret put GOOGLE_OAUTH_CLIENT_SECRET
npx wrangler secret put GOOGLE_ADS_DEVELOPER_TOKEN
npx wrangler secret put TOKEN_ENCRYPTION_KEY
npx wrangler secret put MCP_COOKIE_ENCRYPTION_KEY

3. Migrate and deploy

npx wrangler d1 migrations apply DB --remote
npm test && npm run deploy

wrangler deploy prints the live URL. Put that URL in vars.PUBLIC_BASE_URL in wrangler.jsonc and deploy once more, so the OAuth metadata advertises the right issuer.

4. Google Cloud Console

In the OAuth client (type: Web application) add the authorised redirect URI:

https://<your-worker-url>/google/callback

Publish the OAuth consent screen with the scope https://www.googleapis.com/auth/adwords, and point the privacy policy and terms links at /privacy and /terms.

5. Custom domain (optional, last)

Once grenadastudio.com is on Cloudflare, uncomment the routes block in wrangler.jsonc, set PUBLIC_BASE_URL to https://google-ads-mcp.grenadastudio.com, redeploy, and add the matching redirect URI in Google Cloud Console.


Connecting a client

Add the remote MCP server:

https://<your-worker-url>/mcp

The client performs dynamic client registration at /register, is redirected through /authorize to Google, and receives its token from /token. One Google sign-in covers both the MCP session and the Google Ads grant.

Verify manually with the MCP Inspector, or directly:

curl -s https://<your-worker-url>/health

Rollback

npx wrangler deployments list
npx wrangler rollback --message "reverting to previous version"

D1 migrations are additive; rolling the Worker back does not require a schema change.


Project layout

src/
  index.ts                  HTTP router
  mcp/
    server.ts               JSON-RPC dispatch, tool middleware, audit
    permissions.ts          risk map + rate limits
    confirmations.ts        two-phase confirmation for high-risk writes
    tools/                  accounts, campaigns, ad-groups, ads, keywords, reports,
                            assets, audiences, billing, extensions, pmax, advanced
  auth/
    mcp-oauth.ts            OAuth 2.1 authorization server for MCP clients
    google-oauth.ts         Google consent, code exchange, refresh, revoke
    token-store.ts          encrypted refresh token + cached access token
  google-ads/
    client.ts               single REST choke point, version from env
    account-discovery.ts    listAccessibleCustomers + customer_client tree walk
    account-resolver.ts     ownership check + login-customer-id decision
    gaql.ts                 read-only GAQL sanitiser, date filters
    mutate.ts               mutate helpers, atomic multi-resource writes, update masks
    api-docs.ts             curated GAQL reference used by query_google_ads_api_docs
    errors.ts               Google error -> internal error code
  db/repositories.ts        D1 access
  db/audit.ts               audit log + redaction
  crypto/aes-gcm.ts         AES-GCM, HMAC, SHA-256, base64, PKCE helpers
  routes/pages.ts           home, privacy, terms, data deletion, account
  routes/google.ts          connect, callback, disconnect, refresh
migrations/0001_initial.sql
migrations/0002_feedback.sql
test/                       122 unit + security tests

Acceptance checklist

  • [x] Worker bundles and runs (wrangler deploy --dry-run, wrangler dev)
  • [x] /health responds
  • [x] POST /mcp returns 401 with WWW-Authenticate when unauthenticated
  • [x] OAuth discovery metadata served at both .well-known paths
  • [x] Dynamic client registration works; unknown clients are rejected at /authorize
  • [x] PKCE S256 required; redirect URIs matched exactly
  • [x] initialize and tools/list answer over /mcp (62 tools)
  • [x] Structured error envelopes (GOOGLE_NOT_CONNECTED, VALIDATION_ERROR, RATE_LIMITED)
  • [x] Rate limiting verified live: the 21st raw-GAQL call in a minute is rejected
  • [x] Confirmation lifecycle verified live: token issued → tampered arguments rejected → identical arguments accepted → replay of the same token rejected
  • [x] Manager-account guard verified live on a reporting tool
  • [x] Google consent URL carries access_type=offline, prompt=consent, include_granted_scopes=true
  • [x] D1 migrations apply from scratch
  • [x] No secrets in the repository
  • [x] Privacy, Terms and Data deletion pages present
  • [x] npm run typecheck clean, npm test green (122 tests)
  • [ ] End-to-end run against real Google credentials — needs the real developer token and OAuth client

Recommended Servers

playwright-mcp

playwright-mcp

A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.

Official
Featured
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.

Official
Featured
Local
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.

Official
Featured
Local
TypeScript
VeyraX MCP

VeyraX MCP

Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.

Official
Featured
Local
graphlit-mcp-server

graphlit-mcp-server

The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.

Official
Featured
TypeScript
Kagi MCP Server

Kagi MCP Server

An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.

Official
Featured
Python
E2B

E2B

Using MCP to run code via e2b.

Official
Featured
Neon Database

Neon Database

MCP server for interacting with Neon Management API and databases

Official
Featured
Exa Search

Exa Search

A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.

Official
Featured
Qdrant Server

Qdrant Server

This repository is an example of how to create a MCP server for Qdrant, a vector search engine.

Official
Featured