Google Ads MCP Server
Provides read-only access to Google Ads data, including account summaries, campaign performance, search terms, and conversion actions.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@Google Ads MCP ServerShow me my top search terms for last month"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Google Ads MCP Server
A read-only Model Context Protocol server for the Google Ads API, built with Node.js + TypeScript and deployable to Railway as a standard HTTP service.
It exposes seven reporting tools over the MCP Streamable HTTP transport, plus a built-in OAuth flow for minting the Google Ads refresh token during setup.
This server contains no write tools. Every tool reads; nothing creates, updates, or deletes.
Endpoints
Method | Path | Auth | Purpose |
GET |
| none | Liveness + which config values are present |
GET |
|
| Starts Google OAuth consent |
GET |
| single-use | Exchanges the code and displays the refresh token |
POST |
| none | MCP JSON-RPC endpoint |
GET |
| none | Endpoint index |
/health never returns secret values — only booleans indicating whether each variable is set.
⚠️ The MCP endpoint is unauthenticated
POST /mcprequires no credentials. Anyone who knows this server's URL can read every Google Ads account the configured refresh token can reach — spend, search terms, conversion actions.This is deliberate. The Claude custom connector UI accepts only an OAuth client ID and secret and provides no way to attach a custom
Authorizationheader, so a bearer-token gate made the server impossible to add as a connector.
MCP_AUTH_TOKENstill exists and is still required — it guards/auth, which displays a Google refresh token. It no longer has any effect on/mcp.See Hardening a public endpoint for ways to reduce the exposure without breaking connector support.
Related MCP server: google-ads-mcp
Tools
Tool | What it returns |
| Accounts the authorized credentials can reach directly |
| Client accounts beneath a manager (MCC), with name, currency, time zone, status, level |
| Account settings + aggregate performance for a date range + campaign counts by status |
| Campaign configuration: status, channel type, bidding strategy, budget, flight dates |
| Campaign metrics over a date range, optionally segmented by date/week/month/device/network |
| Actual search queries with metrics, match type, and the keyword each one matched |
| Conversion actions with type, category, counting method, lookback windows, default value |
Shared conventions:
Customer IDs may be passed with or without dashes (
123-456-7890or1234567890).Date ranges accept either a
date_rangeconstant (LAST_30_DAYS,THIS_MONTH, …) or an explicitstart_date+end_datepair inYYYY-MM-DD.Money is returned in the account's currency, already converted from micros.
Derived metrics (CTR, average CPC, cost per conversion, ROAS, conversion rate) are computed from the raw counters in the same response, so they always agree with them.
Every tool is annotated
readOnlyHint: true.
Queries are assembled server-side from validated, allow-listed inputs — no caller-supplied GAQL is ever executed.
Setup
1. Google Cloud OAuth client
Enable the Google Ads API in your Google Cloud project.
Create an OAuth 2.0 Client ID of type Web application.
Add this Authorized redirect URI, matching
GOOGLE_REDIRECT_URIexactly:https://g-ads-production.up.railway.app/oauth2callbackNote the client ID and client secret.
If your OAuth consent screen is in Testing mode, add yourself as a test user — otherwise refresh tokens expire after seven days.
2. Google Ads developer token
Google Ads → Tools & Settings → API Center (on a manager account). A Basic Access token is enough for reporting; Test Account tokens only work against test accounts.
3. Deploy to Railway
Point a Railway service at this repository. railway.json sets the build command, start command,
and a /health healthcheck; Railway injects PORT automatically.
Nixpacks runs three phases, and railway.json must not duplicate any of them:
Phase | Command | Comes from |
install |
| Nixpacks default, because |
build |
|
|
start |
|
|
Do not put npm ci in buildCommand. Running it twice makes the second pass try to remove a
node_modules/.cache directory the first pass still holds open, and the build fails with
EBUSY: resource busy or locked, rmdir '/app/node_modules/.cache'.
Node is pinned to 20 by engines.node (20.x) in package.json, with .nvmrc matching. Nixpacks
reads engines.node first; an open range like >=20.0.0 lets it select the newest available Node
instead. @types/node is held on ^20 so the types match the runtime.
Set these service variables:
Variable | Required | Notes |
| at boot | OAuth web client ID |
| at boot | OAuth web client secret |
| at boot | Must exactly match the URI on the OAuth client |
| at boot | Guards |
| for tools | From the Google Ads API Center |
| for tools | Produced by step 4 below |
| for MCC use | Manager account ID, digits only |
| optional | Per-IP cap on |
The server boots with only the four boot-critical variables set, so you can run the OAuth flow
before you have a refresh token. Tools return a clear configuration error until the rest are set,
and /health reports status: "degraded".
Missing a boot-critical variable exits with a descriptive log line rather than serving traffic.
4. Mint the refresh token
Open in a browser:
https://g-ads-production.up.railway.app/auth?token=YOUR_MCP_AUTH_TOKENThe flow requests scope https://www.googleapis.com/auth/adwords with access_type=offline and
prompt=consent. After you approve, the callback page displays the refresh token once. Copy it into
Railway as GOOGLE_ADS_REFRESH_TOKEN and redeploy.
The token is displayed only — this server never stores or logs it. Treat the page like a password prompt and close it when you're done.
If no refresh token comes back, revoke the app at
myaccount.google.com/permissions and re-run /auth.
5. Verify
curl https://g-ads-production.up.railway.app/healthstatus should be ok and every entry under configured should be true.
Connecting an MCP client
No credentials are needed — just the URL.
Claude custom connector
Settings → Connectors → Add custom connector, then enter:
https://g-ads-production.up.railway.app/mcpLeave the OAuth Client ID and Client Secret fields blank. The server does not advertise an OAuth authorization server, so the connector attaches directly over Streamable HTTP.
Those fields are for authenticating the connector to this server. They are unrelated to
GOOGLE_ADS_CLIENT_ID / GOOGLE_ADS_CLIENT_SECRET, which authenticate this server to Google and
belong in Railway's variables. Do not paste your Google credentials into the connector UI.
Config-file clients
{
"mcpServers": {
"google-ads": {
"type": "http",
"url": "https://g-ads-production.up.railway.app/mcp"
}
}
}For Claude Code:
claude mcp add --transport http google-ads \
https://g-ads-production.up.railway.app/mcpQuick manual check:
curl -s -X POST https://g-ads-production.up.railway.app/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'Local development
npm install
cp .env.example .env # fill it in; .env is gitignored
npm run dev # tsx watch on http://localhost:8080npm run typecheck # tsc --noEmit
npm run build # compile to dist/
npm start # run the compiled serverUse Node 20 locally to match production (nvm use picks it up from .nvmrc). Newer Node still
works, but npm will warn EBADENGINE against the pinned engines.node.
For local OAuth, add http://localhost:8080/oauth2callback as a second authorized redirect URI on
the OAuth client and set GOOGLE_REDIRECT_URI to match.
Set LOG_LEVEL=debug to log every generated GAQL query — the fastest way to diagnose an API
rejection.
Architecture
src/
index.ts entry point, lifecycle, signal handling
server.ts Express app: routes, health, MCP transport wiring
mcp.ts MCP server construction
config.ts environment loading and validation
auth.ts constant-time bearer-token checks
oauth.ts /auth and /oauth2callback, CSRF state store
logger.ts structured JSON logging
google-ads/
client.ts Google Ads client, error normalisation, enum decoding
gaql.ts validated query assembly, date-range handling
format.ts micros/int64 conversion, metric derivation
tools/
shared.ts schemas, handler wrapper, result helpers
index.ts tool registration
<one file per tool>Stateless MCP transport. Each POST to /mcp gets a fresh McpServer and
StreamableHTTPServerTransport with sessionIdGenerator: undefined. No session affinity is needed,
so Railway can restart or scale the service without stranding in-flight sessions. GET/DELETE on
/mcp return 405 with an explanatory JSON-RPC error rather than a bare 404.
Error handling. Tool failures return MCP error results (isError: true) with an actionable
message, not transport exceptions. GoogleAdsFailure responses are unpacked into their individual
error messages and codes.
Google Ads API version. Pinned by google-ads-api@24 (currently v24). All selected field paths
were validated against the v24 protobuf descriptors. When bumping the client major version, re-check
field names — v21, for example, replaced campaign.start_date with campaign.start_date_time.
Security notes
POST /mcpis unauthenticated. Anyone with the URL can read the authorized Google Ads accounts. See the warning under Endpoints and the hardening options below./authstill requiresMCP_AUTH_TOKEN, compared in constant time, because/oauth2callbackdisplays a refresh token.OAuth uses single-use, 10-minute, cryptographically random
statevalues. Because they live in process memory, a redeploy between/authand/oauth2callbackinvalidates the flow — just start over. The same applies if you run more than one replica.The refresh token is displayed once and never persisted or logged by this server.
No Google credential — client secret, developer token, or refresh token — is ever returned by any endpoint.
/healthreports presence booleans only. Tool errors carry Google's own message text and the names of missing variables, never their values.Every tool is read-only, which bounds the damage from the open endpoint to disclosure. Do not add write tools while
/mcpis unauthenticated — that would let anonymous callers change live ad spend.No secrets are committed;
.envis gitignored and.env.examplecontains placeholders only.
Hardening a public endpoint
All of these keep Claude connector compatibility:
Secret URL. Move the endpoint to an unguessable path (
/mcp/<random>) and treat the URL as the credential. Connectors accept any URL, so this costs nothing at the client. It is bearer-token security with the token in the path — keep it out of screenshots and logs.Rate limiting. On by default: 120 requests/minute per IP, tunable with
MCP_RATE_LIMIT_PER_MINUTE(0disables). This is a quota and cost backstop, not access control.Network restrictions. Put the service behind Cloudflare Access or a similar reverse proxy that can allow-list by IP or identity ahead of Railway.
Proper OAuth. The spec-correct fix is to implement the MCP authorization spec so the server acts as an OAuth 2.0 resource server — that is exactly what the connector's Client ID and Client Secret fields are for. It is a real piece of work (metadata discovery, client registration, authorize/token endpoints, PKCE, token validation) and is not implemented here.
Scope the credentials. Authorize the refresh token against only the accounts this server needs, rather than a top-level MCC, so an exposed endpoint reveals less.
A note on the client library
Google does not publish an official Node.js client for the Google Ads API (its official libraries
cover Java, .NET, PHP, Python, Ruby and Perl). This server uses
google-ads-api, the de-facto standard community
client, which wraps Google's own generated google-ads-node gRPC bindings. OAuth uses Google's
official google-auth-library.
License
MIT
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- AlicenseAqualityFmaintenanceA read-write MCP server for managing Google Ads campaigns, ad groups, keywords, and ads via natural language.122The Unlicense

google-ads-mcpofficial
Alicense-qualityAmaintenanceMCP server that provides tools and resources for interacting with Google Ads API, enabling search, metadata retrieval, and account management through natural language.843Apache 2.0- AlicenseBqualityBmaintenanceRead-only MCP server for Google Ads, enabling querying campaigns, ad groups, ads, insights, and keywords without create/update/delete operations.9MIT
- AlicenseAqualityBmaintenanceA read-only MCP server for querying Google Ads data using GAQL, enabling AI assistants to safely read campaign performance, ad groups, and keywords.12MIT
Related MCP Connectors
Read-only MCP server for ClassQuill, a tutoring-business-management platform.
Read-only Yandex Metrika MCP. Query visits, sources, geo, devices and more in plain language.
Read-only MCP server for wafergraph.com's semiconductor & AI supply-chain data: 30 tools, no auth.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/administrator-prog/g-ads-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server