Camberstack: a Google Ads MCP server
Connect Google Ads (and, optionally, Search Console) to Claude, ChatGPT, Cursor or any MCP client. Your AI reads the account with read-only tools and can propose changes, from negative keywords to whole Search, Performance Max, Display and YouTube campaigns. Nothing changes in the account until you approve a specific proposal, and every applied change can be undone in one call, on every plan. Risky changes (turning a campaign on, more than doubling a budget or bid, switching bid strategy) are flagged in the proposal before you approve. A demo account lets anyone try every tool first.
Hosted: https://camberstack.io/mcp (Free: 1 Google Ads account at a time with one switch a month, every tool, unlimited changes; Pro $15/mo: up to 10 accounts).
Setup guides, with screenshots and troubleshooting: Connect Google Ads to Claude · Connect Google Ads to ChatGPT · every tool · Cursor, VS Code and others
Tools
The Search Console tools take either days or exact start_date/end_date, and fresh: true to include the last 2-3
days Google is still filling in. Requesting indexing and submitting sitemaps aren't available: Camberstack only holds
the read-only scope.
The Google Analytics tools need analytics.readonly and analytics.edit (Google-verified sensitive scopes). Sign-in
asks for them alongside Ads and Search Console; a connection made before they were added gets a personal
/connect/analytics link on its first Analytics call, which adds both to the same Google account. The Cloud project
needs the Google Analytics Data API and Admin API enabled.
What a proposal can do:
-
Control: add negative keywords; pause or enable a campaign, ad group, keyword or ad; set a campaign's daily budget (never a shared budget) or its end date.
-
Build, always created paused (building and spending are two separate approvals):
- a Search campaign: ad groups, keywords, responsive search ads, targeting, negatives, sitelinks;
- a Performance Max campaign: asset groups of text, images and YouTube videos, with the business name and logo on the campaign as Google's brand guidelines require (no shopping feed);
- a Display campaign: responsive display ads;
- a Demand Gen campaign with YouTube video ads, the API's only way onto YouTube (it refuses to create
Video campaigns:
MUTATE_NOT_ALLOWED, v23).
Images are existing image assets or https URLs, which are downloaded SSRF-safe (
src/fetch-safe.ts) and uploaded. A proposal can also add an ad group, keywords, an ad or sitelinks to an existing campaign. -
Tune: keyword and ad group bids, bid strategy, location and language targeting, presence-only location mode, final URL suffix.
-
Measure: create a conversion action, set its counting, import a GA4 key event, scope a campaign to one conversion goal.
Risky changes carry a ⚠ line in the proposal summary: enabling a campaign, more than doubling a budget or a bid, switching bid strategy, removing the last location, a new primary conversion action. Undo removes only what Camberstack itself created; nothing the user built is ever deleted. (Undoing a GA4 import removes the action, since Google can't hide it again; importing again restores it.) Search Console is read-only.
How auth works
Camberstack is an OAuth 2.1 authorization server for MCP clients (dynamic client registration,
PKCE). Its /authorize sends the user to Google for adwords + openid email, plus webmasters.readonly
(Search Console). Either is enough to connect ("à la carte"): a tool that needs a missing one, or Google Analytics, returns a personal
/connect/<google-ads|search-console|analytics> link that adds just that service to the same Google account. The Google refresh
token is stored AES-256-GCM encrypted and never leaves the server. The MCP client gets
Camberstack's own tokens, which are stored only as SHA-256 hashes.
Two MCP SDKs, on purpose: /mcp is served by SDK v2 (@modelcontextprotocol/server + /node), which
answers both 2026-07-28 clients (server/discover) and 2025-era ones (initialize, kept on a
JSON-response transport). The authorization server (mcpAuthRouter, requireBearerAuth) stays on
@modelcontextprotocol/sdk 1.x, because v2 ships only the resource-server half. Don't drop 1.x
until v2 (or a companion package) has /authorize, /token and /register.
Run it
npm ci && npm test
ENCRYPTION_KEY=$(openssl rand -hex 32) \
GOOGLE_CLIENT_ID=… GOOGLE_CLIENT_SECRET=… GOOGLE_ADS_DEVELOPER_TOKEN=… \
BASE_URL=https://your.host npm run dev
The hosted HTTP server is npm run start:http (the Docker image runs node dist/server.js).
npm start is a stdio mode that needs no env, database or Google login: it lists every tool for
directory inspectors (Glama runs mcp-proxy -- pnpm run start), and any tool call answers with
the hosted URL to connect to. pnpm-workspace.yaml turns off native builds for pnpm installs, so
stdio mode installs anywhere; production installs with npm.
scripts/try-account.ts runs the read tools against a real account from an existing refresh
token, skipping OAuth. Use it for dogfooding.
License
MIT