google-ads-mcp-worker

google-ads-mcp-worker

Remote MCP server for the Google Ads API running on Cloudflare Workers. Provides read-only access to Google Ads data through GAQL, including customer listing, MCC expansion, paginated search, and resource metadata retrieval.

Category
访问服务器

README

google-ads-mcp-worker

A remote MCP server for the Google Ads API, running on Cloudflare Workers.

Read-only access to your Google Ads data through GAQL, reachable over HTTPS from any MCP client — no local process, no Python environment, no container. Implements the 2026-07-28 MCP specification: stateless Streamable HTTP, server/discover, no session ids.

Status: early. The protocol layer is exercised by tests and by the smoke script below. The tool surface is small on purpose and will grow with real use. Issues and PRs welcome.

Why this exists

Google ships an official MCP server (Python, Apache-2.0). It is good, and if you want a local stdio server you should probably use it. This project is a different shape:

googleads/google-ads-mcp this project
Runtime Python, local stdio (remote via Cloud Run) Cloudflare Workers, remote-first
MCP revision 2025-era FastMCP 2026-07-28 (stateless, server/discover)
Ads API version pinned in code (v24 at time of writing) v25, set by a config var — bump without a code change
Ops you run and patch a process wrangler deploy, then nothing
Cold start Python interpreter edge isolate, ~ms
Auth to the server OAuth proxy Cloudflare Access JWT, bearer token, or open
MCC expansion not exposed list_customer_clients

Neither replaces the other. Use whichever matches how you deploy.

Tools

All four are read-only (readOnlyHint: true). This server exposes no mutating tools — it never calls a mutate endpoint, so it cannot change your campaigns, budgets, or bids.

Tool What it does
list_accessible_customers Customer ids the credentials can reach directly. Start here.
list_customer_clients Expand a manager (MCC) account into its client accounts, with name, currency and time zone.
search Run a GAQL query against one account. Paginated, with next_page_token.
get_resource_metadata Selectable/filterable/sortable fields of a resource, plus compatible metrics and segments.

The server also ships instructions (served in server/discover) telling the model the things that otherwise cost a failed query each: metrics don't exist on manager accounts, money is in micros, query fields are snake_case while the response is camelCase.

Quickstart

1. Google Ads credentials

You need four values:

  • Developer token — Google Ads manager account → Tools & Settings → API Center. Note the access levels: Test reaches test accounts only, Explorer is capped at 2,880 operations/day and blocks several services, Basic gives 15,000/day, Standard is uncapped.

  • OAuth client id and secret — Google Cloud Console → APIs & Services → Credentials → OAuth client ID, with the Google Ads API enabled on the project. Choose Desktop app unless you have a reason not to: it allows the loopback redirect the helper below uses, with nothing to register.

  • Refresh token — for the scope https://www.googleapis.com/auth/adwords. If you don't have one:

    node scripts/get-refresh-token.mjs path/to/your-oauth-client.json
    

    It runs the consent flow against a local loopback and writes the client id, secret and refresh token into .dev.vars — no copy-pasting a token through your scrollback. Pass --print if you'd rather handle it yourself.

Two things that catch people out:

  • A service account will not work without domain-wide delegation. The Google Ads API expects a user credential.
  • While your OAuth consent screen is in Testing, refresh tokens expire after 7 days. Publish the app before you rely on one for a deployed server, or the Worker starts failing with invalid_grant a week later.

2. Install and configure

git clone https://github.com/marcosvb1/google-ads-mcp-worker
cd google-ads-mcp-worker
npm install
cp .dev.vars.example .dev.vars   # fill it in

3. Run it locally

npm run dev

Or check everything in one command — it starts a server, smoke-tests it and shuts it down:

npm run verify

To poke at it by hand, point the MCP Inspector at http://localhost:8787/mcp (npm run inspect). ./scripts/smoke.sh tests a server you are already running, or a deployed one:

./scripts/smoke.sh https://your-worker.workers.dev "$TOKEN"

4. Deploy

Load the four Google secrets straight from a credentials file, without them passing through your terminal:

./scripts/set-secrets.sh path/to/credentials.json

Or set each one by hand:

npx wrangler secret put GOOGLE_ADS_DEVELOPER_TOKEN
npx wrangler secret put GOOGLE_ADS_CLIENT_ID
npx wrangler secret put GOOGLE_ADS_CLIENT_SECRET
npx wrangler secret put GOOGLE_ADS_REFRESH_TOKEN

Then pick an auth gate — without one the server refuses every request — and ship:

npx wrangler secret put MCP_SHARED_SECRET
npx wrangler deploy

Serving more than one manager account

Developer tokens are issued per manager account and carry their own daily operation quota, and a client's MCC usually arrives with its own token and its own login. So "which credentials" is a property of the account being queried, not of the deployment.

Suffix any credential variable with _<PROFILE> to add a profile:

# default profile
npx wrangler secret put GOOGLE_ADS_DEVELOPER_TOKEN

# profile "sirio" — its own token and manager, reusing the default login
npx wrangler secret put GOOGLE_ADS_DEVELOPER_TOKEN_SIRIO
# GOOGLE_ADS_LOGIN_CUSTOMER_ID_SIRIO goes in wrangler.jsonc (not a secret)

Or in one step, per profile:

./scripts/set-secrets.sh --profile sirio path/to/credentials.json

Then every tool takes a profile argument. It is typed as an enum of the profiles you actually configured, so a wrong value fails schema validation with the real list rather than as a confusing permission error from Google. Anything a profile leaves unset is inherited from the default, so sharing one Google login across several managers costs one variable per profile. Results echo back which profile served them.

With a single profile configured, the argument is not advertised at all — nothing to think about until you need it.

Authentication

Two separate questions, easy to conflate:

  1. How the Worker talks to Google — one set of credentials, held as Worker secrets. Every caller of this server shares that identity.
  2. Who may call the Worker — configured below. Because of (1), this is the gate that matters: whoever reaches /mcp can read whatever those credentials can read.

Three modes, resolved in order. With none configured the server denies every request — that is deliberate.

Mode Set How callers authenticate
Cloudflare Access ACCESS_TEAM_DOMAIN + ACCESS_AUD Access does the OAuth; the Worker verifies the Cf-Access-Jwt-Assertion JWT against your team's JWKS
Bearer token MCP_SHARED_SECRET Authorization: Bearer <token>
Open MCP_ALLOW_UNAUTHENTICATED="true" none — local development only

Narrow the blast radius further with GOOGLE_ADS_ALLOWED_CUSTOMER_IDS, a comma-separated allowlist of the accounts this deployment may query. Any other id is refused before a request reaches Google.

If you put it behind a Cloudflare MCP Portal

A MCP Server Portal gives you tool curation, per-call logging and a single endpoint for several servers. It is not a security perimeter for this Worker: Cloudflare's own documentation warns that blocked users can still reach the server's direct URL and bypass the Access policy. Keep MCP_SHARED_SECRET set (the portal stores it as the upstream credential), or configure Access on the Worker itself.

Connecting a client

{
  "mcpServers": {
    "google-ads": {
      "type": "http",
      "url": "https://google-ads-mcp-worker.<your-subdomain>.workers.dev/mcp",
      "headers": { "Authorization": "Bearer <MCP_SHARED_SECRET>" }
    }
  }
}

Clients that predate the 2026-07-28 revision still work: the handler serves 2025-era requests through its stateless fallback. GET and DELETE on /mcp answer 405, since those were session operations and there are no sessions.

Configuration reference

Variable Kind Default Notes
GOOGLE_ADS_DEVELOPER_TOKEN secret — required
GOOGLE_ADS_CLIENT_ID secret — required
GOOGLE_ADS_CLIENT_SECRET secret — required
GOOGLE_ADS_REFRESH_TOKEN secret — required, scope adwords
GOOGLE_ADS_LOGIN_CUSTOMER_ID var — manager account id, digits only
GOOGLE_ADS_API_VERSION var v25 see the version note below
GOOGLE_ADS_ALLOWED_CUSTOMER_IDS var — comma-separated allowlist
ACCESS_TEAM_DOMAIN / ACCESS_AUD var — enables Cloudflare Access mode
MCP_SHARED_SECRET secret — enables bearer mode
MCP_ALLOW_UNAUTHENTICATED var — "true" disables the gate

Any of the five GOOGLE_ADS_* credential variables may carry a _<PROFILE> suffix to define an additional profile — see above. GOOGLE_ADS_API_VERSION and GOOGLE_ADS_ALLOWED_CUSTOMER_IDS are deployment-wide and are never suffixed.

Secrets may also be supplied through the Secrets Store; the code reads either a plain string binding or a store binding.

Things that will bite you

Collected from the API's sharper edges — most of them cost somebody a debugging session.

  • API versions are retired abruptly. On 2026-08-07, v21 began returning Version v21 is deprecated. Requests to this version will be blocked. mid-session, and a validateOnly dry run had passed minutes earlier. A clean dry run does not guarantee the next call succeeds. That is why the version is a var: bump GOOGLE_ADS_API_VERSION and redeploy. Check the sunset dates.
  • login-customer-id is required for MCC access. Reaching a client account through a manager without it fails with USER_PERMISSION_DENIED, and the failure does not name the missing header.
  • Manager accounts have no metrics. Query a client account; use list_customer_clients to find one.
  • Query is snake_case, response is camelCase. cost_micros in GAQL comes back as costMicros. Read the returned fieldMask.
  • Money is micros. Divide by 1,000,000.
  • Brazil: Google does not pass PIS/COFINS through to the advertiser, so cost_micros / 1e6 is the amount actually billed — no gross-up. This is not symmetric with Meta Ads, which does pass tax through. A report combining both channels must not apply the same factor to both.
  • Pages cap at 10,000 rows. Ignoring next_page_token silently truncates large queries.
  • Rate limits are a token bucket, not a fixed number. Expect RESOURCE_TEMPORARILY_EXHAUSTED under load and back off.
  • A Worker has 6 simultaneous outbound connections. Relevant if you fan out across many accounts.

Design notes

  • googleAds:search, not searchStream. searchStream returns the whole result set in one response — and, uniquely in this API, wrapped in a JSON array rather than an object. That is a poor fit for a 128 MB Worker and gives the caller no way to stop early. Paging keeps every response bounded.
  • No Durable Object. MCP 2026-07-28 is stateless, and this server is a thin REST proxy that holds nothing between requests. A fresh McpServer is built per request; the handler is built once per isolate so that subscriptions/listen streams are not orphaned.
  • No nodejs_compat. @modelcontextprotocol/server has a workerd export condition and bundles a @cfworker/json-schema validator, because Ajv compiles with new Function, which workerd forbids.
  • Access tokens are cached per isolate and refreshed a minute early. It is only ever a cache — a cold isolate simply fetches again.
  • Response size is capped at 60k characters, and the tool says so rather than returning truncated (invalid) JSON.

Contributing

See CONTRIBUTING.md. The short version: npm run typecheck && npm test must pass, and new tools stay read-only unless there is a discussion first.

License

Apache-2.0. Not an official Google product, and not affiliated with Google.

推荐服务器

Baidu Map

Baidu Map

百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。

官方
精选
JavaScript
Playwright MCP Server

Playwright MCP Server

一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。

官方
精选
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。

官方
精选
本地
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。

官方
精选
本地
TypeScript
VeyraX

VeyraX

一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。

官方
精选
本地
graphlit-mcp-server

graphlit-mcp-server

模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。

官方
精选
TypeScript
Kagi MCP Server

Kagi MCP Server

一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。

官方
精选
Python
e2b-mcp-server

e2b-mcp-server

使用 MCP 通过 e2b 运行代码。

官方
精选
Neon MCP Server

Neon MCP Server

用于与 Neon 管理 API 和数据库交互的 MCP 服务器

官方
精选
Exa MCP Server

Exa MCP Server

模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。

官方
精选