@aligulzar729/google-ads-mcp 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +391 -0
  3. package/dist/index.js +4494 -0
  4. package/package.json +73 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ali Gulzar
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,391 @@
1
+ # google-ads-mcp
2
+
3
+ [![CI](https://github.com/aligulzar729/google-ads-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/aligulzar729/google-ads-mcp/actions/workflows/ci.yml)
4
+ [![npm](https://img.shields.io/npm/v/@aligulzar729/google-ads-mcp.svg)](https://www.npmjs.com/package/@aligulzar729/google-ads-mcp)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
6
+
7
+ Run your Google Ads account from Claude, Codex, or any AI assistant, in plain English.
8
+
9
+ Ask for a report, launch a campaign, tune bids and budgets, manage keywords, or build Performance Max assets, all by chatting. It's an [MCP](https://modelcontextprotocol.io) server, so it plugs into any AI harness that speaks MCP: Claude Code, Claude Desktop, OpenAI Codex, Cursor, LibreChat, and others.
10
+
11
+ The important part: reads are free and can never spend, and every write **previews first**. The assistant shows you the exact values and asks you to type **yes** or **ok** in chat; only then does it apply with `confirmed:true` and `user_confirmation`. New campaigns start paused. You get an AI copilot for Google Ads without pretending the model is a free pass on your budget.
12
+
13
+ > **Money and residual risk:** once something is enabled or a budget goes up, Google can spend real money. Previews and yes/ok are guardrails, not a bank lock, MCP cannot force a human click, and a sloppy model could invent approval. Read the preview. If you would not hit Apply in the Google Ads UI, do not type yes here. See [About money](#about-money) and [SECURITY.md](SECURITY.md).
14
+
15
+ ```
16
+ You: How did my campaigns do last month?
17
+ AI: (pulls a report: clicks, cost, conversions, ROAS by campaign)
18
+
19
+ You: Create a paused search campaign "Summer Sale" with a $20/day budget.
20
+ AI: (shows a preview of exactly what it will create)
21
+ Type yes or ok to create it, or anything else to cancel.
22
+ You: yes
23
+ AI: Created "Summer Sale" (PAUSED). It won't serve until you enable it.
24
+ ```
25
+
26
+ ## Quick start
27
+
28
+ ```bash
29
+ # 1) Credentials: developer token + OAuth client + refresh token + customer id
30
+ # (see Credentials below). Put them in your shell or MCP client env.
31
+
32
+ # 2) Run via npx (no install), stdio is the default
33
+ npx -y @aligulzar729/google-ads-mcp --help
34
+
35
+ # 3) Or clone and develop
36
+ git clone https://github.com/aligulzar729/google-ads-mcp.git
37
+ cd google-ads-mcp
38
+ npm ci
39
+ cp .env.example .env # fill in values
40
+ npm run dev:stdio # or: npm run dev:http
41
+ ```
42
+
43
+ Node **22.5+** is required (HTTP mode uses Node's built-in SQLite).
44
+
45
+ ## What it can do
46
+
47
+ - **Reporting**: campaign performance, Search impression share, Performance Max asset groups, and raw GAQL for anything else. It looks up real field names first, so queries don't fail on guessed fields.
48
+ - **Campaigns**: create Search, Display, Demand Gen, Video, App, Shopping, and Performance Max campaigns; rename, set dates and networks, pause and resume, set budgets and bidding strategies, and target by location, language, and schedule.
49
+ - **Ad groups and ads**: manage ad groups and build ads per channel (responsive search, responsive display, HTML5, Demand Gen, video, app, shopping).
50
+ - **Keywords**: list with quality score, add positive or negative keywords, change bids and status.
51
+ - **Performance Max**: create asset groups from text and images, update or delete them, add more images, list and remove assets.
52
+ - **Accounts**: check the connection, list the accounts you can reach, switch the active one, and read an audit trail of every change.
53
+
54
+ ## What this is not
55
+
56
+ - **Not the Google Ads UI.** There is no full visual editor, no Auction Insights dashboard, no Billing center. For some jobs you still open ads.google.com.
57
+ - **Not an autopilot.** It does not decide strategy for you, watch ROAS overnight, or "just run ads." You (or a human who knows the account) still choose what to build and what to enable.
58
+ - **Not a guarantee of perfect model behavior.** Tool text tells the assistant to preview and wait for your yes/ok. Most models will. Some will not. Stay in the loop on anything that spends or deletes.
59
+ - **Not a substitute for access control.** Whoever can talk to this server with your credentials can attempt the same changes you can. Use least-privilege Google users, and do not point a public bot at a high-spend account for fun.
60
+ - **Not offline magic.** It talks to the real Google Ads API. Bad GAQL, wrong customer id, or a Testing-mode OAuth app will fail the same way they would in any other client.
61
+
62
+ If you want a report in chat, a careful campaign draft, or help wiring keywords and P-Max assets with a human still holding the wheel, you are in the right place. If you want unsupervised budget AI, you are not.
63
+
64
+ ## About money
65
+
66
+ Reads never cost anything. Writes can.
67
+
68
+ | Action | Money impact |
69
+ |--------|----------------|
70
+ | Reports, lookups, list ads, connection status | None. Free. |
71
+ | Create campaign / P-Max asset group | **Paused by default**, no serving, no spend until something is enabled. |
72
+ | Raise daily budget, change bids, enable a campaign or ad group | Can spend as soon as Google serves. |
73
+ | Pause or remove | Stops or deletes serving; removes are permanent for that entity. |
74
+
75
+ A few blunt habits that save people pain:
76
+
77
+ - Start on a **test or low-budget** account while you learn the tools.
78
+ - Treat **yes** like approving a wire: skim the preview numbers (budget, bid, status) before you type it.
79
+ - Prefer **PAUSED** creates, then enable only what you meant to turn on.
80
+ - Shared budgets are shared, changing one campaign's budget can hit every campaign on that budget (the tools warn when they can).
81
+ - Disabling chat confirm (`REQUIRE_USER_CONFIRMATION=false`) is for trusted automation only. On a live account with real spend, leave it on.
82
+
83
+ You are responsible for the Google Ads account and any charges that come from it. This project is MIT software that talks to Google's API; it does not pay your media bill and it does not absorb mistakes.
84
+
85
+ ## Safe by default
86
+
87
+ - Reads never cost money. Reporting, lookups, and status checks are always free.
88
+ - Every write **previews first**. The model must show you the values and ask you to type **yes** or **ok**. Apply requires `confirmed:true` and `user_confirmation` `"yes"` or `"ok"` (`REQUIRE_USER_CONFIRMATION`, default on).
89
+ - New campaigns and Performance Max asset groups are created **paused**, so they never serve or spend until you turn them on.
90
+ - Deletes show a clear warning before they run (gated by `REQUIRE_USER_CONFIRMATION`, default on).
91
+ - Every applied change is recorded in a durable audit log.
92
+ - HTTP mode rate-limits OAuth and `/mcp` per client IP (process-local; put a reverse proxy in front for multi-replica).
93
+
94
+ Trusted headless automation only: set `REQUIRE_USER_CONFIRMATION=false` so one-shot `confirmed:true` works without a chat yes/ok. Do that only when you already trust the caller with the account.
95
+
96
+ ## Requirements
97
+
98
+ - Node 22.5 or newer.
99
+ - Google Ads API credentials (see [Credentials](#credentials)).
100
+
101
+ No native build step and no database to run: it uses Node's built-in SQLite, so `npx` just works.
102
+
103
+ ## Add it to your AI harness
104
+
105
+ For a single account on your own machine, most people use the stdio mode below. For a shared, multi-user deployment where each person signs in with their own Google account, see [Hosted, multi-user](#hosted-multi-user).
106
+
107
+ <details>
108
+ <summary><b>Claude Code</b></summary>
109
+
110
+ <br>
111
+
112
+ Add it from the CLI (put the server name right after `add`, then the env vars):
113
+
114
+ ```bash
115
+ claude mcp add google-ads \
116
+ --env GOOGLE_ADS_DEVELOPER_TOKEN=... \
117
+ --env GOOGLE_OAUTH_CLIENT_ID=... \
118
+ --env GOOGLE_OAUTH_CLIENT_SECRET=... \
119
+ --env GOOGLE_ADS_REFRESH_TOKEN=... \
120
+ --env GOOGLE_ADS_CUSTOMER_ID=... \
121
+ -- npx -y @aligulzar729/google-ads-mcp
122
+ ```
123
+
124
+ Or add it to `.mcp.json` (shared with a project) or `~/.claude.json` (just you):
125
+
126
+ ```json
127
+ {
128
+ "mcpServers": {
129
+ "google-ads": {
130
+ "command": "npx",
131
+ "args": ["-y", "@aligulzar729/google-ads-mcp"],
132
+ "env": {
133
+ "GOOGLE_ADS_DEVELOPER_TOKEN": "...",
134
+ "GOOGLE_OAUTH_CLIENT_ID": "...",
135
+ "GOOGLE_OAUTH_CLIENT_SECRET": "...",
136
+ "GOOGLE_ADS_REFRESH_TOKEN": "...",
137
+ "GOOGLE_ADS_CUSTOMER_ID": "..."
138
+ }
139
+ }
140
+ }
141
+ }
142
+ ```
143
+ </details>
144
+
145
+ <details>
146
+ <summary><b>Claude Desktop</b></summary>
147
+
148
+ <br>
149
+
150
+ Edit the config file (Settings, then Developer, then Edit Config), or open it directly:
151
+
152
+ - macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
153
+ - Windows: `%APPDATA%\Claude\claude_desktop_config.json`
154
+
155
+ ```json
156
+ {
157
+ "mcpServers": {
158
+ "google-ads": {
159
+ "command": "npx",
160
+ "args": ["-y", "@aligulzar729/google-ads-mcp"],
161
+ "env": {
162
+ "GOOGLE_ADS_DEVELOPER_TOKEN": "...",
163
+ "GOOGLE_OAUTH_CLIENT_ID": "...",
164
+ "GOOGLE_OAUTH_CLIENT_SECRET": "...",
165
+ "GOOGLE_ADS_REFRESH_TOKEN": "...",
166
+ "GOOGLE_ADS_CUSTOMER_ID": "..."
167
+ }
168
+ }
169
+ }
170
+ }
171
+ ```
172
+
173
+ Restart Claude Desktop after editing.
174
+ </details>
175
+
176
+ <details>
177
+ <summary><b>OpenAI Codex</b></summary>
178
+
179
+ <br>
180
+
181
+ Add it from the CLI:
182
+
183
+ ```bash
184
+ codex mcp add google-ads -- npx -y @aligulzar729/google-ads-mcp
185
+ ```
186
+
187
+ Or edit `~/.codex/config.toml` directly. The table name must be `mcp_servers` with an underscore:
188
+
189
+ ```toml
190
+ [mcp_servers.google-ads]
191
+ command = "npx"
192
+ args = ["-y", "@aligulzar729/google-ads-mcp"]
193
+ env = { GOOGLE_ADS_DEVELOPER_TOKEN = "...", GOOGLE_OAUTH_CLIENT_ID = "...", GOOGLE_OAUTH_CLIENT_SECRET = "...", GOOGLE_ADS_REFRESH_TOKEN = "...", GOOGLE_ADS_CUSTOMER_ID = "..." }
194
+ ```
195
+ </details>
196
+
197
+ <details>
198
+ <summary><b>LibreChat (hosted, multi-user)</b></summary>
199
+
200
+ <br>
201
+
202
+ LibreChat connects over HTTP and lets each user sign in with their own Google account, so you run the server yourself (see [Hosted, multi-user](#hosted-multi-user)) and point LibreChat at it. A ready-to-use `librechat.yaml` snippet ships in this repo:
203
+
204
+ ```yaml
205
+ mcpServers:
206
+ GoogleAds:
207
+ type: streamable-http
208
+ url: "https://your-subdomain.example.com/mcp"
209
+ startup: false
210
+ ```
211
+
212
+ `startup: false` matters: this server uses OAuth, so it should connect when a user first signs in, not at boot. Also add the host to `mcpSettings.allowedDomains` in `librechat.yaml`. Users then click Connect in chat and approve at Google. Nothing to paste.
213
+ </details>
214
+
215
+ <details>
216
+ <summary><b>Cursor / VS Code</b></summary>
217
+
218
+ <br>
219
+
220
+ Add an MCP server entry (Cursor: Settings → MCP; VS Code Copilot: MCP config) equivalent to:
221
+
222
+ ```json
223
+ {
224
+ "mcpServers": {
225
+ "google-ads": {
226
+ "command": "npx",
227
+ "args": ["-y", "@aligulzar729/google-ads-mcp"],
228
+ "env": {
229
+ "GOOGLE_ADS_DEVELOPER_TOKEN": "...",
230
+ "GOOGLE_OAUTH_CLIENT_ID": "...",
231
+ "GOOGLE_OAUTH_CLIENT_SECRET": "...",
232
+ "GOOGLE_ADS_REFRESH_TOKEN": "...",
233
+ "GOOGLE_ADS_CUSTOMER_ID": "..."
234
+ }
235
+ }
236
+ }
237
+ }
238
+ ```
239
+
240
+ Exact JSON key names vary slightly by client (`mcpServers` vs `servers`); keep `command` / `args` / `env` the same.
241
+ </details>
242
+
243
+ <details>
244
+ <summary><b>Any other MCP client</b></summary>
245
+
246
+ <br>
247
+
248
+ Single user, over stdio: run `npx -y @aligulzar729/google-ads-mcp` with the five `GOOGLE_ADS_*` / `GOOGLE_OAUTH_*` env vars set, using whatever `command` / `args` / `env` shape your client expects.
249
+
250
+ Multi-user, over HTTP: run it in `--http` mode behind HTTPS and point your client at `https://<host>/mcp`. Any client that supports streamable-http with OAuth (dynamic client registration plus PKCE) can connect. See [Hosted, multi-user](#hosted-multi-user).
251
+ </details>
252
+
253
+ ## Credentials
254
+
255
+ Three values are always required, because they identify the app (not the user):
256
+
257
+ - `GOOGLE_ADS_DEVELOPER_TOKEN`: from your Google Ads manager account, under API Center. Apply for Standard Access before you run production volume.
258
+ - `GOOGLE_OAUTH_CLIENT_ID` and `GOOGLE_OAUTH_CLIENT_SECRET`: an OAuth client from a Google Cloud project that has the Google Ads API enabled.
259
+
260
+ For local (stdio) use, add one account's own credentials:
261
+
262
+ - `GOOGLE_ADS_REFRESH_TOKEN`: a refresh token for a Google account with access to the ads account. You can mint one with the [OAuth 2.0 Playground](https://developers.google.com/oauthplayground) using the `https://www.googleapis.com/auth/adwords` scope.
263
+ - `GOOGLE_ADS_CUSTOMER_ID`: the 10-digit account id, no dashes.
264
+ - `GOOGLE_ADS_LOGIN_CUSTOMER_ID`: optional, set it to your manager (MCC) id if you access clients through a manager account.
265
+
266
+ Keep the OAuth app in the **In Production** publishing status. In Testing, refresh tokens expire after 7 days.
267
+
268
+ For hosted (http) use you don't set a refresh token at all. Users authorize themselves in the browser and the server stores each token encrypted. See below.
269
+
270
+ ## Configuration
271
+
272
+ Everything is set through environment variables, and every setting can also be passed as a command-line flag (useful in a client's `args` or a launch command). The flag is the env var name, lowercased, with dashes for underscores:
273
+
274
+ ```bash
275
+ npx -y @aligulzar729/google-ads-mcp \
276
+ --app-name="Acme Ads" \
277
+ --log-level=debug \
278
+ --no-require-user-confirmation # trusted automation only
279
+ ```
280
+
281
+ In a client config, add them to `args`:
282
+
283
+ ```json
284
+ "args": ["-y", "@aligulzar729/google-ads-mcp", "--app-name=Acme Ads"]
285
+ ```
286
+
287
+ | Setting | Env var (or flag) | Notes |
288
+ |---------|-------------------|-------|
289
+ | App name | `APP_NAME` / `--app-name` | shown on the OAuth consent and error pages |
290
+ | Log level | `LOG_LEVEL` / `--log-level` | `debug`, `info`, `warn`, `error` |
291
+ | Require confirmation | `REQUIRE_USER_CONFIRMATION` / `--no-require-user-confirmation` | default on; gates every write, including deletes; off only for trusted headless |
292
+ | HTTP port | `PORT` / `--port` | default 3001 |
293
+
294
+ Credentials and keys (`GOOGLE_ADS_*`, `GOOGLE_OAUTH_*`, `TOKEN_ENC_KEY`) work as flags too, but prefer env vars for secrets: flags can show up in the process list.
295
+
296
+ ## Hosted, multi-user
297
+
298
+ In `--http` mode the server is its own OAuth 2.1 authorization server that proxies Google. Each user signs in once, and their Google refresh token is stored encrypted on the server and never handed back to the client. This is how you offer Google Ads to a whole team from a shared chat app like LibreChat.
299
+
300
+ Run it (behind HTTPS, on its own subdomain):
301
+
302
+ ```bash
303
+ npx -y @aligulzar729/google-ads-mcp --http
304
+ ```
305
+
306
+ Extra environment it needs in this mode:
307
+
308
+ - `TOKEN_ENC_KEY`: 32-byte base64 key that encrypts stored refresh tokens. Generate with `openssl rand -base64 32`. Keep it stable and secret; rotating it invalidates every stored authorization.
309
+ - `PUBLIC_BASE_URL`: the public HTTPS base URL of this instance, for example `https://ads.example.com`. Register `${PUBLIC_BASE_URL}/auth/callback` as a redirect URI on your Google OAuth client.
310
+ - `ALLOWED_REDIRECT_ORIGINS`: comma-separated allowlist of client origins allowed to complete the OAuth flow, for example `https://chat.example.com`. This is what stops a phished authorize link from delivering a code to an attacker.
311
+ - `PORT`: defaults to 3001.
312
+ - `AUDIT_DB_PATH`: where the audit log and encrypted authorizations live. Mount a volume so it survives restarts.
313
+
314
+ A `Dockerfile` and `docker-compose.yml` are included for a container deployment.
315
+
316
+ ### Docker Compose (HTTP)
317
+
318
+ ```bash
319
+ cp .env.example .env
320
+ # set GOOGLE_ADS_DEVELOPER_TOKEN, GOOGLE_OAUTH_*, TOKEN_ENC_KEY,
321
+ # PUBLIC_BASE_URL=https://ads.example.com, ALLOWED_REDIRECT_ORIGINS=https://chat.example.com
322
+
323
+ docker compose up -d --build
324
+ curl -sS http://127.0.0.1:3001/healthz
325
+ ```
326
+
327
+ Point your reverse proxy (TLS) at `127.0.0.1:3001`. The **whole subdomain** must reach this container: OAuth discovery, `/authorize`, `/token`, and `/mcp` all live on the same host (`PUBLIC_BASE_URL`). Back up the `ads_data` volume (audit log + encrypted authorizations).
328
+
329
+ ## Tools
330
+
331
+ Seven tools, each takes an `action`:
332
+
333
+ | Tool | Actions | What it covers |
334
+ |------|---------|----------------|
335
+ | `google_ads_account` | `status`, `list`, `switch`, `audit` | connection and token check, accounts you can reach, switch the active account, read the audit trail |
336
+ | `google_ads_report` | `campaign_performance`, `impression_share`, `asset_group`, `gaql`, `metadata` | curated campaign report, Search impression share, Performance Max asset-group performance, raw GAQL, and field discovery |
337
+ | `google_ads_campaign` | `create`, `update`, `status`, `budget`, `bidding`, `targeting` | create any campaign type, rename and set dates and networks, pause and resume, budgets, bidding, and location/language/schedule targeting |
338
+ | `google_ads_ad_group` | `create`, `update`, `status`, `list_ads`, `add_ad`, `ad_status` | manage ad groups and their ads, including per-channel ad builders via `ad_type` |
339
+ | `google_ads_keyword` | `list`, `add`, `update` | list with quality score, add positive or negative, change bid or status |
340
+ | `google_ads_pmax` | `create_asset_group`, `asset_group`, `add_images`, `list_assets`, `remove_asset` | create an asset group from text and images (images are required up front and it starts paused), update or delete a group, add images, list and remove assets |
341
+ | `google_ads_lookup` | `location`, `language` | turn a location or language name into the numeric id that targeting and geo reports need |
342
+
343
+ A few things worth knowing: ROAS is computed as conversion value over cost. Average Position is gone (Google removed it), so use `impression_share`. Performance Max has no ad-level reporting, so use `gaql` on its asset groups.
344
+
345
+ ## Big reports and pagination
346
+
347
+ Reports are bounded per response and paged without hitting Google again:
348
+
349
+ 1. The first call fetches one bounded page and asks Google for the true total row count in the same request, so a capped report reads `top 200 of 47,310` instead of quietly cutting off.
350
+ 2. Leftover rows are parked in a short-lived (about 5 minutes), per-account cache.
351
+ 3. If more rows remain, the reply ends with a `cursor`. The model asks whether you want the rest, then continues from cache with zero extra Google calls, so paging can't cause rate limiting.
352
+
353
+ One report run is bounded to `limit` rows by design. To go wider, tighten the query or aggregate in GAQL. For genuinely huge extracts, use an export rather than inline rows.
354
+
355
+ ## Scheduled and unattended use
356
+
357
+ Reporting is read-only and retried, so it's reliable for cron and scheduled jobs once the OAuth app is In Production.
358
+
359
+ - A dead refresh token returns a clear "reconnect Google Ads" message and is written to the audit log with `category:"auth"`, so you can alert on it.
360
+ - Call `google_ads_account` with `status` at the start of a routine to fail fast.
361
+ - Quota and rate errors are retried with backoff, and if they still fail you get a clear "needs Standard Access" message.
362
+
363
+ ## Troubleshooting
364
+
365
+ | Symptom | What to try |
366
+ |---------|-------------|
367
+ | Refresh token dies after ~7 days | OAuth app is still in **Testing**. Publish it to **In Production**. |
368
+ | `PERMISSION_DENIED` / empty account list | User lacks access to that customer id; or set `GOOGLE_ADS_LOGIN_CUSTOMER_ID` to the MCC id when accessing clients through a manager. |
369
+ | Quota / rate errors | Developer token may need **Standard Access**. Reads are retried with backoff. |
370
+ | "user_confirmation" / confirmed rejected | Call once without `confirmed` to preview, ask the user to type yes or ok, then re-call with `confirmed:true` and `user_confirmation:"yes"` (or `"ok"`). |
371
+ | "not connected" / reconnect message | Refresh token revoked; re-auth (HTTP) or mint a new refresh token (stdio). |
372
+ | HTTP container exits on boot | Missing `TOKEN_ENC_KEY`, `PUBLIC_BASE_URL`, or `ALLOWED_REDIRECT_ORIGINS`. Check logs. |
373
+ | OAuth works but LibreChat never connects | `startup: false` on the MCP server entry; host in `allowedDomains`; URL must be the public `/mcp` path. |
374
+
375
+ ## Notes
376
+
377
+ - The underlying client, `google-ads-api` (Opteo), is the community Node library for the advertiser API; Google doesn't ship an official one. It targets API v23. The high-level query and mutate calls used here are version-agnostic, but check method signatures if you upgrade the library.
378
+ - Error messages and tool text are in English.
379
+ - See [SECURITY.md](SECURITY.md) for disclosure, residual risk, and key-compromise playbook. See [CHANGELOG.md](CHANGELOG.md) for release notes.
380
+
381
+ ## Acknowledgments
382
+
383
+ This project is built on [`google-ads-api`](https://github.com/Opteo/google-ads-api), the community Node client for the Google Ads API maintained by the team at [Opteo](https://opteo.com). Google ships no official Node library for the advertiser API, and Opteo's package fills that gap with a well-typed, carefully maintained interface that handles the hard parts (OAuth, GAQL, mutates, and the Google Ads protobufs) so this server does not have to. A sincere thank you to the Opteo maintainers and contributors: this MCP server stands on their work.
384
+
385
+ ## License
386
+
387
+ MIT
388
+
389
+ ## Contributing
390
+
391
+ See [CONTRIBUTING.md](CONTRIBUTING.md).