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.
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_insightsreturns 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_docsanswers 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]
/healthresponds - [x]
POST /mcpreturns 401 withWWW-Authenticatewhen unauthenticated - [x] OAuth discovery metadata served at both
.well-knownpaths - [x] Dynamic client registration works; unknown clients are rejected at
/authorize - [x] PKCE
S256required; redirect URIs matched exactly - [x]
initializeandtools/listanswer 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 typecheckclean,npm testgreen (122 tests) - [ ] End-to-end run against real Google credentials — needs the real developer token and OAuth client
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。
Neon MCP Server
用于与 Neon 管理 API 和数据库交互的 MCP 服务器
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。