{"_id":"@aligulzar729/google-ads-mcp","_rev":"4-e97d9f0384bd8312311c1117e8565770","name":"@aligulzar729/google-ads-mcp","dist-tags":{"latest":"0.4.0"},"versions":{"0.1.0":{"name":"@aligulzar729/google-ads-mcp","version":"0.1.0","keywords":["google-ads","google-ads-api","mcp","model-context-protocol","claude","codex","librechat","advertising","ppc","ai-agent"],"author":{"name":"Ali Gulzar","email":"ali.gulzar729@gmail.com"},"license":"MIT","_id":"@aligulzar729/google-ads-mcp@0.1.0","maintainers":[{"name":"aligulzar729","email":"ali.gulzar729@gmail.com"}],"homepage":"https://github.com/aligulzar729/google-ads-mcp#readme","bugs":{"url":"https://github.com/aligulzar729/google-ads-mcp/issues"},"bin":{"google-ads-mcp":"dist/index.js"},"dist":{"shasum":"df63f60799c210caf036be92e12d482054c63dbd","tarball":"https://registry.npmjs.org/@aligulzar729/google-ads-mcp/-/google-ads-mcp-0.1.0.tgz","fileCount":4,"integrity":"sha512-BO5VIAvCvNEwtZCpROsIgMotuxX88CZvakRXC3Ygk66W4wRT5S7SOouc9hSkMvtLIPwEMvJxGTzNbDG7t/Ra0g==","signatures":[{"sig":"MEYCIQCI1qTTuVLztgjKWVzUXpgqZU2jJ1bnjxTJ0W+2UTAHbwIhALDBXPPkCLLi6voyrgDJVwQUtDqvwzVtwzhq39YBod2/","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":247971},"type":"module","engines":{"node":">=22.5"},"gitHead":"73f35ea3a893e0c827465676948e5e2567fec4ea","scripts":{"lint":"eslint --ext .ts src","test":"vitest run","build":"tsup","start":"node dist/index.js","dev:http":"tsx watch src/index.ts --http","dev:stdio":"tsx src/index.ts --stdio","typecheck":"tsc --noEmit","prepublishOnly":"npm run build && npm test"},"_npmUser":{"name":"aligulzar729","email":"ali.gulzar729@gmail.com"},"repository":{"url":"git+https://github.com/aligulzar729/google-ads-mcp.git","type":"git"},"_npmVersion":"10.9.8","description":"Manage Google Ads from Claude, Codex, LibreChat, or any MCP client. Natural-language reporting plus guarded campaign, keyword, and Performance Max operations.","directories":{},"_nodeVersion":"22.23.0","dependencies":{"zod":"^3.24.1","hono":"^4.12.9","google-ads-api":"^23.0.0","@hono/node-server":"^1.19.12","google-auth-library":"^9.15.0","@modelcontextprotocol/sdk":"^1.29.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.19.2","tsup":"^8.3.5","eslint":"^8.57.1","vitest":"^2.1.8","typescript":"^5.7.2","@types/node":"^22.10.0","eslint-plugin-import":"^2.31.0","@typescript-eslint/parser":"^7.18.0","@typescript-eslint/eslint-plugin":"^7.18.0","eslint-import-resolver-typescript":"^3.7.0"},"_npmOperationalInternal":{"tmp":"tmp/google-ads-mcp_0.1.0_1783581391548_0.6095626384857122","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@aligulzar729/google-ads-mcp","version":"0.2.0","keywords":["google-ads","google-ads-api","mcp","model-context-protocol","claude","codex","librechat","advertising","ppc","ai-agent"],"author":{"name":"Ali Gulzar","email":"ali.gulzar729@gmail.com"},"license":"MIT","_id":"@aligulzar729/google-ads-mcp@0.2.0","maintainers":[{"name":"aligulzar729","email":"ali.gulzar729@gmail.com"}],"homepage":"https://github.com/aligulzar729/google-ads-mcp#readme","bugs":{"url":"https://github.com/aligulzar729/google-ads-mcp/issues"},"bin":{"google-ads-mcp":"dist/index.js"},"dist":{"shasum":"748bef0e6be58a368064b0902fccad95f4a44571","tarball":"https://registry.npmjs.org/@aligulzar729/google-ads-mcp/-/google-ads-mcp-0.2.0.tgz","fileCount":4,"integrity":"sha512-MByySrue7Q0IqW2Kwr7oaH9ht5QHguRy8PWLlJnKNdjsdxCV9GCTJlKJL1yO7T2t8+pPcWTWFqd0Fe2tfm1qNQ==","signatures":[{"sig":"MEUCIDu4Ihlmy0WJKigMjJE1LhKaaawiw7NYycSHdBO6J/QaAiEAiwH+IevuT92xRm4mE60i1FAcaDCpzJDMMHBmaUEdUkE=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@aligulzar729%2fgoogle-ads-mcp@0.2.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":247826},"type":"module","engines":{"node":">=22.5"},"gitHead":"26efec1cd815da673d2cf5b3f540e9149811b625","scripts":{"lint":"eslint src","test":"vitest run","build":"tsup","start":"node dist/index.js","dev:http":"tsx watch src/index.ts --http","dev:stdio":"tsx src/index.ts --stdio","typecheck":"tsc --noEmit","prepublishOnly":"npm run build && npm test"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:3425e10c-293c-43e2-b44c-75151d3c3884"}},"repository":{"url":"git+https://github.com/aligulzar729/google-ads-mcp.git","type":"git"},"_npmVersion":"11.16.0","description":"Manage Google Ads from Claude, Codex, LibreChat, or any MCP client. Natural-language reporting plus guarded campaign, keyword, and Performance Max operations.","directories":{},"_nodeVersion":"24.18.0","dependencies":{"zod":"^4.4.3","hono":"^4.12.28","google-ads-api":"^23.0.0","@hono/node-server":"^1.19.12","google-auth-library":"^9.15.0","@modelcontextprotocol/sdk":"^1.29.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.23.0","tsup":"^8.3.5","eslint":"^10.6.0","vitest":"^2.1.8","globals":"^17.7.0","@eslint/js":"^10.0.1","typescript":"^5.7.2","@types/node":"^22.10.0","typescript-eslint":"^8.63.0"},"_npmOperationalInternal":{"tmp":"tmp/google-ads-mcp_0.2.0_1783583768833_0.6333308544654792","host":"s3://npm-registry-packages-npm-production"}},"0.3.0":{"name":"@aligulzar729/google-ads-mcp","version":"0.3.0","keywords":["google-ads","google-ads-api","mcp","model-context-protocol","claude","codex","librechat","advertising","ppc","ai-agent"],"author":{"name":"Ali Gulzar","email":"ali.gulzar729@gmail.com"},"license":"MIT","_id":"@aligulzar729/google-ads-mcp@0.3.0","maintainers":[{"name":"aligulzar729","email":"ali.gulzar729@gmail.com"}],"homepage":"https://github.com/aligulzar729/google-ads-mcp#readme","bugs":{"url":"https://github.com/aligulzar729/google-ads-mcp/issues"},"bin":{"google-ads-mcp":"dist/index.js"},"dist":{"shasum":"ebc28987b4aef117dcdbf4d96f3cd66951f39115","tarball":"https://registry.npmjs.org/@aligulzar729/google-ads-mcp/-/google-ads-mcp-0.3.0.tgz","fileCount":4,"integrity":"sha512-zsOTPV1jgB7g6FaVV4JlVOiMVzN0bKsn4rhUcqZEQAVaWxnSkcr16Y3MneX5Evwd18qAT+at/WxKLLRf3bqMxQ==","signatures":[{"sig":"MEUCIQCDX8X5/vZ/HHMJDmqyo/13cxPOeS2gnXlSUppjXmTxCwIgH3As4M/Y6wVLVivy3Y5+bRa3WkAg1poHWo7k3r7e5pg=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@aligulzar729%2fgoogle-ads-mcp@0.3.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":256934},"type":"module","engines":{"node":">=22.5"},"gitHead":"6f55efd019441ac4eb7c36f7546d3afaf2c126a8","scripts":{"lint":"eslint src","test":"vitest run","build":"tsup","start":"node dist/index.js","dev:http":"tsx watch src/index.ts --http","dev:stdio":"tsx src/index.ts --stdio","typecheck":"tsc --noEmit","prepublishOnly":"npm run build && npm test"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:3425e10c-293c-43e2-b44c-75151d3c3884"}},"repository":{"url":"git+https://github.com/aligulzar729/google-ads-mcp.git","type":"git"},"_npmVersion":"11.16.0","description":"Manage Google Ads from Claude, Codex, LibreChat, or any MCP client. Natural-language reporting plus guarded campaign, keyword, and Performance Max operations.","directories":{},"_nodeVersion":"24.18.0","dependencies":{"zod":"^4.4.3","hono":"^4.12.28","google-ads-api":"^24.1.0","@hono/node-server":"^2.0.8","google-auth-library":"^9.15.0","@modelcontextprotocol/sdk":"^1.29.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.23.0","tsup":"^8.3.5","eslint":"^10.6.0","vitest":"^4.1.10","globals":"^17.7.0","@eslint/js":"^10.0.1","typescript":"~6.0.3","@types/node":"^26.1.1","typescript-eslint":"^8.63.0"},"_npmOperationalInternal":{"tmp":"tmp/google-ads-mcp_0.3.0_1783927795799_0.9948111699930735","host":"s3://npm-registry-packages-npm-production"}},"0.4.0":{"name":"@aligulzar729/google-ads-mcp","version":"0.4.0","description":"Manage Google Ads from Claude, Codex, LibreChat, or any MCP client. Natural-language reporting plus guarded campaign, keyword, and Performance Max operations.","type":"module","bin":{"google-ads-mcp":"dist/index.js"},"engines":{"node":">=22.5"},"scripts":{"build":"tsup","dev:http":"tsx watch src/index.ts --http","dev:stdio":"tsx src/index.ts --stdio","start":"node dist/index.js","typecheck":"tsc --noEmit","lint":"eslint src","test":"vitest run","prepublishOnly":"npm run build && npm test"},"keywords":["google-ads","google-ads-api","mcp","model-context-protocol","claude","codex","librechat","advertising","ppc","ai-agent"],"author":{"name":"Ali Gulzar","email":"ali.gulzar729@gmail.com"},"license":"MIT","repository":{"type":"git","url":"git+https://github.com/aligulzar729/google-ads-mcp.git"},"bugs":{"url":"https://github.com/aligulzar729/google-ads-mcp/issues"},"homepage":"https://github.com/aligulzar729/google-ads-mcp#readme","publishConfig":{"access":"public"},"dependencies":{"@hono/node-server":"^2.0.8","@modelcontextprotocol/sdk":"^1.29.0","google-ads-api":"^24.1.0","google-auth-library":"^9.15.0","hono":"^4.12.28","zod":"^4.4.3"},"devDependencies":{"@eslint/js":"^10.0.1","@types/node":"^26.1.1","eslint":"^10.6.0","globals":"^17.7.0","tsup":"^8.3.5","tsx":"^4.23.0","typescript":"~6.0.3","typescript-eslint":"^8.63.0","vitest":"^4.1.10"},"gitHead":"963a5897b3646e32d3f74f87a383a0f460335d52","_id":"@aligulzar729/google-ads-mcp@0.4.0","_nodeVersion":"24.18.0","_npmVersion":"11.16.0","dist":{"integrity":"sha512-TlRIyxZoZGHjzFTouh2Xz9qbyq+7x0z/8VnB3wRBdpoa7m1xeMEac7dx7TRendbL2uFgcZKVY93/d3OZBYeTzw==","shasum":"82bb17a4aa274bd83b0c7f349547ab040e4e369d","tarball":"https://registry.npmjs.org/@aligulzar729/google-ads-mcp/-/google-ads-mcp-0.4.0.tgz","fileCount":4,"unpackedSize":260546,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@aligulzar729%2fgoogle-ads-mcp@0.4.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIE80x39YJny/QYcOIJhR6N0V+YixO0l0Xv6YoXDqAZ8BAiEA+jo+tnWwH+b94Eszs14RgPTIu65GGSkN4IMN0U/kgQU="}]},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:3425e10c-293c-43e2-b44c-75151d3c3884"}},"directories":{},"maintainers":[{"name":"aligulzar729","email":"ali.gulzar729@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/google-ads-mcp_0.4.0_1783997476147_0.20917893530402742"},"_hasShrinkwrap":false}},"time":{"created":"2026-07-09T07:16:31.335Z","modified":"2026-07-14T02:51:16.610Z","0.1.0":"2026-07-09T07:16:31.705Z","0.2.0":"2026-07-09T07:56:08.962Z","0.3.0":"2026-07-13T07:29:55.953Z","0.4.0":"2026-07-14T02:51:16.305Z"},"bugs":{"url":"https://github.com/aligulzar729/google-ads-mcp/issues"},"author":{"name":"Ali Gulzar","email":"ali.gulzar729@gmail.com"},"license":"MIT","homepage":"https://github.com/aligulzar729/google-ads-mcp#readme","keywords":["google-ads","google-ads-api","mcp","model-context-protocol","claude","codex","librechat","advertising","ppc","ai-agent"],"repository":{"type":"git","url":"git+https://github.com/aligulzar729/google-ads-mcp.git"},"description":"Manage Google Ads from Claude, Codex, LibreChat, or any MCP client. Natural-language reporting plus guarded campaign, keyword, and Performance Max operations.","maintainers":[{"name":"aligulzar729","email":"ali.gulzar729@gmail.com"}],"readme":"# google-ads-mcp\n\n[![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)\n[![npm](https://img.shields.io/npm/v/@aligulzar729/google-ads-mcp.svg)](https://www.npmjs.com/package/@aligulzar729/google-ads-mcp)\n[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)\n\nRun your Google Ads account from Claude, Codex, or any AI assistant, in plain English.\n\nAsk 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.\n\nThe 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.\n\n> **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 [Money and safety](#money-and-safety) and [SECURITY.md](SECURITY.md).\n\n```\nYou:  How did my campaigns do last month?\nAI:   (pulls a report: clicks, cost, conversions, ROAS by campaign)\n\nYou:  Create a paused search campaign \"Summer Sale\" with a $20/day budget.\nAI:   (shows a preview of exactly what it will create)\n      Type yes or ok to create it, or anything else to cancel.\nYou:  yes\nAI:   Created \"Summer Sale\" (PAUSED). It won't serve until you enable it.\n```\n\n## Contents\n\n- [Quick start](#quick-start)\n- [What it can do](#what-it-can-do)\n- [What this is not](#what-this-is-not)\n- [Money and safety](#money-and-safety)\n- [Add it to your AI harness](#add-it-to-your-ai-harness)\n- [Credentials](#credentials) (and [Getting a refresh token](#getting-a-refresh-token-oauth-playground))\n- [Configuration](#configuration) (and [all environment variables](#all-environment-variables))\n- [Hosted, multi-user](#hosted-multi-user) (and [LibreChat on the same host](#librechat-on-the-same-host-no-public-url))\n- [Tools](#tools)\n- [Coverage](#coverage)\n- [Big reports and pagination](#big-reports-and-pagination)\n- [Scheduled and unattended use](#scheduled-and-unattended-use)\n- [Troubleshooting](#troubleshooting)\n- [Notes](#notes)\n- [Acknowledgments](#acknowledgments)\n- [Disclaimer](#disclaimer)\n- [License](#license)\n- [Contributing](#contributing)\n\n## Quick start\n\n```bash\n# 1) Credentials: developer token + OAuth client + refresh token + customer id\n#    (see Credentials below). Put them in your shell or MCP client env.\n\n# 2) Run via npx (no install), stdio is the default\nnpx -y @aligulzar729/google-ads-mcp --help\n\n# 3) Or clone and develop\ngit clone https://github.com/aligulzar729/google-ads-mcp.git\ncd google-ads-mcp\nnpm ci\ncp .env.example .env   # fill in values\nnpm run dev:stdio      # or: npm run dev:http\n```\n\nNode **22.5+** is required. There is no native build step and no database to run: HTTP mode uses Node's built-in SQLite, so `npx` just works.\n\n## What it can do\n\n- **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.\n- **Campaigns**: create Search, Display, Demand Gen, App, Shopping, and Performance Max campaigns (use Demand Gen for YouTube; Google no longer lets the API create Video campaigns); rename, set dates and networks, pause and resume, set budgets and bidding strategies, and target by location, language, and schedule.\n- **Ad groups and ads**: manage ad groups and build ads per channel (responsive search, responsive display, HTML5, Demand Gen, video, app, shopping).\n- **Keywords**: list with quality score, add positive or negative keywords, change bids and status.\n- **Performance Max**: create asset groups from text and images, update or delete them, add more images, list and remove assets.\n- **Accounts**: check the connection and list the accounts you can reach; on the hosted (HTTP) server, also switch the active account and read an audit trail of every change.\n\n### What it can't do\n\n- **Conversion tracking.** Creating or editing conversion actions and importing offline conversions (it reads conversion metrics, but doesn't set them up).\n- **Audiences and bid adjustments.** Remarketing or customer-match lists, audience targeting, and device, demographic, or audience bid modifiers. Targeting here is location, language, and ad schedule only.\n- **Search ad assets / extensions.** Sitelinks, callouts, structured snippets, call, price, and promotion assets (Performance Max text and image assets are supported).\n- **Account structure and budgets.** Creating customer accounts, managing an MCC hierarchy, or setting up billing and account-level budget orders.\n- **Experiments, drafts, and recommendations.** A/B experiments, campaign drafts, and applying Google's optimization-score recommendations.\n- **Shared library and labels.** Shared budgets, negative-keyword and placement-exclusion sets, and labels.\n- **Keyword Planner.** Keyword ideas and traffic forecasts.\n- **External links.** Merchant Center, Analytics, and YouTube account links (Shopping and Performance Max assume these already exist).\n\n## What this is not\n\n- **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.\n- **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.\n- **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.\n- **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.\n- **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.\n\nIf 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.\n\n## Money and safety\n\nReads never cost anything. Writes can, and every write **previews first**: the model shows the exact values and asks you to type **yes** or **ok**; apply then requires `confirmed:true` and `user_confirmation` `\"yes\"` or `\"ok\"` (`REQUIRE_USER_CONFIRMATION`, default on).\n\n| Action | Money impact |\n|--------|----------------|\n| Reports, lookups, list ads, connection status | None. Free. |\n| Create campaign / P-Max asset group | **Paused by default**, no serving, no spend until something is enabled. |\n| Raise daily budget, change bids, enable a campaign or ad group | Can spend as soon as Google serves. |\n| Pause or remove | Stops or deletes serving; removes are permanent for that entity. |\n\nMore guardrails: new campaigns and P-Max asset groups start **paused**; deletes show a clear warning first (also gated by `REQUIRE_USER_CONFIRMATION`); every applied change lands in a durable audit log; and HTTP mode rate-limits OAuth and `/mcp` per client IP (process-local, put a reverse proxy in front for multi-replica).\n\nHabits that save pain:\n\n- Start on a **test or low-budget** account while you learn the tools.\n- Treat **yes** like approving a wire: skim the preview numbers (budget, bid, status) before you type it.\n- Prefer **PAUSED** creates, then enable only what you meant to turn on.\n- Shared budgets are shared: changing one campaign's budget can hit every campaign on that budget (the tools warn when they can).\n\n`REQUIRE_USER_CONFIRMATION=false` disables the chat yes/ok so one-shot `confirmed:true` works, for trusted headless automation only. On a live account with real spend, leave it on.\n\nYou are responsible for the Google Ads account and any charges from it. This is MIT software that talks to Google's API; it does not pay your media bill and does not absorb mistakes.\n\n## Add it to your AI harness\n\nFor 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).\n\n<details>\n<summary><b>Claude Code</b></summary>\n\n<br>\n\nAdd it from the CLI (put the server name right after `add`, then the env vars):\n\n```bash\nclaude mcp add google-ads \\\n  --env GOOGLE_ADS_DEVELOPER_TOKEN=... \\\n  --env GOOGLE_OAUTH_CLIENT_ID=... \\\n  --env GOOGLE_OAUTH_CLIENT_SECRET=... \\\n  --env GOOGLE_ADS_REFRESH_TOKEN=... \\\n  --env GOOGLE_ADS_CUSTOMER_ID=... \\\n  -- npx -y @aligulzar729/google-ads-mcp\n```\n\nOr add it to `.mcp.json` (shared with a project) or `~/.claude.json` (just you):\n\n```json\n{\n  \"mcpServers\": {\n    \"google-ads\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"@aligulzar729/google-ads-mcp\"],\n      \"env\": {\n        \"GOOGLE_ADS_DEVELOPER_TOKEN\": \"...\",\n        \"GOOGLE_OAUTH_CLIENT_ID\": \"...\",\n        \"GOOGLE_OAUTH_CLIENT_SECRET\": \"...\",\n        \"GOOGLE_ADS_REFRESH_TOKEN\": \"...\",\n        \"GOOGLE_ADS_CUSTOMER_ID\": \"...\"\n      }\n    }\n  }\n}\n```\n</details>\n\n<details>\n<summary><b>Claude Desktop</b></summary>\n\n<br>\n\nEdit the config file (Settings, then Developer, then Edit Config), or open it directly:\n\n- macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`\n- Windows: `%APPDATA%\\Claude\\claude_desktop_config.json`\n\n```json\n{\n  \"mcpServers\": {\n    \"google-ads\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"@aligulzar729/google-ads-mcp\"],\n      \"env\": {\n        \"GOOGLE_ADS_DEVELOPER_TOKEN\": \"...\",\n        \"GOOGLE_OAUTH_CLIENT_ID\": \"...\",\n        \"GOOGLE_OAUTH_CLIENT_SECRET\": \"...\",\n        \"GOOGLE_ADS_REFRESH_TOKEN\": \"...\",\n        \"GOOGLE_ADS_CUSTOMER_ID\": \"...\"\n      }\n    }\n  }\n}\n```\n\nRestart Claude Desktop after editing.\n</details>\n\n<details>\n<summary><b>OpenAI Codex</b></summary>\n\n<br>\n\nAdd it from the CLI:\n\n```bash\ncodex mcp add google-ads -- npx -y @aligulzar729/google-ads-mcp\n```\n\nOr edit `~/.codex/config.toml` directly. The table name must be `mcp_servers` with an underscore:\n\n```toml\n[mcp_servers.google-ads]\ncommand = \"npx\"\nargs = [\"-y\", \"@aligulzar729/google-ads-mcp\"]\nenv = { GOOGLE_ADS_DEVELOPER_TOKEN = \"...\", GOOGLE_OAUTH_CLIENT_ID = \"...\", GOOGLE_OAUTH_CLIENT_SECRET = \"...\", GOOGLE_ADS_REFRESH_TOKEN = \"...\", GOOGLE_ADS_CUSTOMER_ID = \"...\" }\n```\n</details>\n\n<details>\n<summary><b>LibreChat (hosted, multi-user)</b></summary>\n\n<br>\n\nLibreChat 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:\n\n```yaml\nmcpServers:\n  GoogleAds:\n    type: streamable-http\n    url: \"https://your-subdomain.example.com/mcp\"\n    startup: false\n    requiresOAuth: true\n```\n\nTwo settings matter:\n\n- `requiresOAuth: true` declares this a per-user OAuth server. Without it, LibreChat tries to auto-detect OAuth by opening an unauthenticated connection to list tools, which always fails on an auth-walled server and fills your logs with `OAuth not supported in unauthenticated discovery` and repeated `Reinspection failed ... timeout`. The flag skips that probe and shows the sign-in prompt directly.\n- `startup: false` connects on first use, not at boot, since there is no user to authorize at startup.\n\nAlso add the host to `mcpSettings.allowedDomains` in `librechat.yaml`. Users then click Connect in chat and approve at Google. Nothing to paste. To run LibreChat and this server on one machine with no public URL, see [LibreChat on the same host](#librechat-on-the-same-host-no-public-url).\n</details>\n\n<details>\n<summary><b>Cursor / VS Code</b></summary>\n\n<br>\n\nAdd an MCP server entry (Cursor: Settings → MCP; VS Code Copilot: MCP config) equivalent to:\n\n```json\n{\n  \"mcpServers\": {\n    \"google-ads\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"@aligulzar729/google-ads-mcp\"],\n      \"env\": {\n        \"GOOGLE_ADS_DEVELOPER_TOKEN\": \"...\",\n        \"GOOGLE_OAUTH_CLIENT_ID\": \"...\",\n        \"GOOGLE_OAUTH_CLIENT_SECRET\": \"...\",\n        \"GOOGLE_ADS_REFRESH_TOKEN\": \"...\",\n        \"GOOGLE_ADS_CUSTOMER_ID\": \"...\"\n      }\n    }\n  }\n}\n```\n\nExact JSON key names vary slightly by client (`mcpServers` vs `servers`); keep `command` / `args` / `env` the same.\n</details>\n\n<details>\n<summary><b>Any other MCP client</b></summary>\n\n<br>\n\nSingle 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.\n\nMulti-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).\n</details>\n\n## Credentials\n\nThree values are always required, because they identify the app (not the user):\n\n- `GOOGLE_ADS_DEVELOPER_TOKEN`: from your Google Ads manager account, under API Center. Apply for Standard Access before you run production volume.\n- `GOOGLE_OAUTH_CLIENT_ID` and `GOOGLE_OAUTH_CLIENT_SECRET`: an OAuth client from a Google Cloud project that has the Google Ads API enabled.\n\nFor local (stdio) use, add one account's own credentials:\n\n- `GOOGLE_ADS_REFRESH_TOKEN`: a refresh token for a Google account with access to the ads account. See [Getting a refresh token](#getting-a-refresh-token-oauth-playground) below.\n- `GOOGLE_ADS_CUSTOMER_ID`: the account you act on, 10 digits with no dashes. Its campaigns and reports are what you read and change.\n- `GOOGLE_ADS_LOGIN_CUSTOMER_ID`: optional. The manager (MCC) account you reach that account *through*, if any.\n\nThese two trip everyone up, so plainly: `CUSTOMER_ID` is **which account to act on**, `LOGIN_CUSTOMER_ID` is **which manager to go through to reach it**. A manager (MCC) account holds no campaigns itself; it just grants access to the client accounts under it. So:\n\n- Your Google user has **direct** access to the account (it shows as `[client]` in `google_ads_account list`): set `CUSTOMER_ID` to it and **leave `LOGIN_CUSTOMER_ID` empty**.\n- You only reach the account **through a manager**: set `CUSTOMER_ID` to the client account and `LOGIN_CUSTOMER_ID` to that **manager's** id, never to the client id, and never to a manager that does not actually own the client (both cause `PERMISSION_DENIED`).\n\nKeep the OAuth app in the **In Production** publishing status. In Testing, refresh tokens expire after 7 days.\n\nFor 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.\n\n### Getting a refresh token (OAuth Playground)\n\nFor stdio mode you supply one account's refresh token. Two settings are what everyone gets wrong, so both steps below are required:\n\n1. **Add the Playground redirect to your OAuth client.** In Google Cloud Console, open your OAuth client (the one whose id and secret you use) under APIs & Services, then Credentials. Under **Authorized redirect URIs**, add `https://developers.google.com/oauthplayground` and save. Miss this and the Playground fails with `redirect_uri_mismatch`.\n2. **Use your own client in the Playground.** Open the [OAuth 2.0 Playground](https://developers.google.com/oauthplayground), click the **gear** (top right), check **Use your own OAuth credentials**, and paste the same client id and secret. Miss this and the token is minted for Google's demo client, which later fails with `unauthorized_client`.\n3. **Authorize the Ads scope.** In the left panel under \"Input your own scopes\", enter `https://www.googleapis.com/auth/adwords`, click **Authorize APIs**, and sign in with the Google account that can reach the ads account.\n4. **Exchange for the token.** Click **Exchange authorization code for tokens** and copy the `refresh_token` into `GOOGLE_ADS_REFRESH_TOKEN`.\n\nA token works only when both are true: it was minted by *your* client (step 2) and *your* client permits the Playground redirect (step 1). The Playground redirect is only needed for minting, so you can remove it from the client afterward; it is separate from the `PUBLIC_BASE_URL/auth/callback` redirect that http mode registers.\n\n## Configuration\n\nEverything is set through environment variables. Every setting can also be passed as a command-line flag: the flag is the env var name, lowercased, with dashes for underscores. A bare `--flag` sets `true`, and `--no-flag` sets `false`. Flags are handy in a client's `args` or a launch command:\n\n```bash\nnpx -y @aligulzar729/google-ads-mcp \\\n  --app-name=\"Acme Ads\" \\\n  --log-level=debug \\\n  --no-require-user-confirmation      # trusted automation only\n```\n\n```json\n\"args\": [\"-y\", \"@aligulzar729/google-ads-mcp\", \"--app-name=Acme Ads\"]\n```\n\nPrefer env vars for secrets: flags can show up in the process list.\n\n### All environment variables\n\n**App identity, required in both modes.** These identify the app, not the user.\n\n| Env var | Flag | Description |\n|---------|------|-------------|\n| `GOOGLE_ADS_DEVELOPER_TOKEN` | `--google-ads-developer-token` | Google Ads API developer token, from your manager account API Center. |\n| `GOOGLE_OAUTH_CLIENT_ID` | `--google-oauth-client-id` | OAuth client id from a Google Cloud project with the Google Ads API enabled. |\n| `GOOGLE_OAUTH_CLIENT_SECRET` | `--google-oauth-client-secret` | OAuth client secret. |\n\n**Behavior, both modes.**\n\n| Env var | Flag | Default | Description |\n|---------|------|---------|-------------|\n| `APP_NAME` | `--app-name` | `Google Ads MCP` | Name shown on the OAuth consent and error pages. |\n| `LOG_LEVEL` | `--log-level` | `info` | `debug`, `info`, `warn`, or `error`. |\n| `REQUIRE_USER_CONFIRMATION` | `--no-require-user-confirmation` | on | Gates every write, including deletes. Turn off only for trusted headless automation. |\n\n**stdio mode, single user.** One account's own credentials.\n\n| Env var | Flag | Default | Description |\n|---------|------|---------|-------------|\n| `GOOGLE_ADS_REFRESH_TOKEN` | `--google-ads-refresh-token` | empty | Refresh token for a Google account with access to the ads account. See [Getting a refresh token](#getting-a-refresh-token-oauth-playground). |\n| `GOOGLE_ADS_CUSTOMER_ID` | `--google-ads-customer-id` | empty | The account to act on (10 digits, no dashes). |\n| `GOOGLE_ADS_LOGIN_CUSTOMER_ID` | `--google-ads-login-customer-id` | empty | The manager (MCC) to reach it through; leave empty for direct access. See [Credentials](#credentials). |\n\n**http mode, hosted and multi-user.** Per-user credentials come from each user's browser sign-in, so you do not set a refresh token here.\n\n| Env var | Flag | Default | Description |\n|---------|------|---------|-------------|\n| `PORT` | `--port` | `3001` | Listen port. |\n| `PUBLIC_BASE_URL` | `--public-base-url` | required | Public base URL of this instance. Register `PUBLIC_BASE_URL/auth/callback` as a redirect URI on the Google OAuth client. |\n| `TOKEN_ENC_KEY` | `--token-enc-key` | required | 32-byte base64 key (`openssl rand -base64 32`) that encrypts stored refresh tokens. Keep it stable and secret. |\n| `ALLOWED_REDIRECT_ORIGINS` | `--allowed-redirect-origins` | required | Comma-separated allowlist of client origins allowed to complete the OAuth flow, for example `https://chat.example.com`. |\n| `AUDIT_DB_PATH` | `--audit-db-path` | `./data/audit.db` | Where the audit log and encrypted authorizations live. Mount a volume so it survives restarts. |\n\n## Hosted, multi-user\n\nIn `--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.\n\nRun it (behind HTTPS, on its own subdomain):\n\n```bash\nnpx -y @aligulzar729/google-ads-mcp --http\n```\n\nBeyond the shared app credentials, this mode needs `TOKEN_ENC_KEY`, `PUBLIC_BASE_URL`, and `ALLOWED_REDIRECT_ORIGINS`, plus optional `PORT` and `AUDIT_DB_PATH` (see [All environment variables](#all-environment-variables)). Two things worth calling out: register `PUBLIC_BASE_URL/auth/callback` as a redirect URI on your Google OAuth client, and `ALLOWED_REDIRECT_ORIGINS` is the allowlist that stops a phished authorize link from delivering a code to an attacker.\n\nA `Dockerfile` and `docker-compose.yml` are included for a container deployment.\n\n### Docker Compose (HTTP)\n\n```bash\ncp .env.example .env\n# set GOOGLE_ADS_DEVELOPER_TOKEN, GOOGLE_OAUTH_*, TOKEN_ENC_KEY,\n# PUBLIC_BASE_URL=https://ads.example.com, ALLOWED_REDIRECT_ORIGINS=https://chat.example.com\n\ndocker compose up -d --build\ncurl -sS http://127.0.0.1:3001/healthz\n```\n\nPoint 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).\n\n### LibreChat on the same host (no public URL)\n\nTo run LibreChat and this server on one machine over plain HTTP, with no public domain or TLS, run this server as a sidecar that shares the LibreChat api container's network namespace. That makes `http://localhost:3001` the same address for both your browser and LibreChat's backend, and Google allows `http://localhost` as the one non-HTTPS OAuth redirect, so sign-in works without a certificate.\n\nAdd to LibreChat's `docker-compose.override.yml`:\n\n```yaml\nservices:\n  api:\n    ports:\n      - \"3001:3001\"                 # expose the sidecar to your browser\n  google-ads-mcp:\n    image: node:24-bookworm-slim\n    command: [\"npx\", \"-y\", \"@aligulzar729/google-ads-mcp\", \"--http\"]\n    network_mode: \"service:api\"     # share the api netns: one localhost for browser and backend\n    depends_on:\n      - api\n    environment:\n      PORT: \"3001\"\n      PUBLIC_BASE_URL: \"http://localhost:3001\"\n      ALLOWED_REDIRECT_ORIGINS: \"http://localhost:3080\"   # your LibreChat origin\n      GOOGLE_ADS_DEVELOPER_TOKEN: ${GOOGLE_ADS_DEVELOPER_TOKEN}\n      GOOGLE_OAUTH_CLIENT_ID: ${GOOGLE_OAUTH_CLIENT_ID}\n      GOOGLE_OAUTH_CLIENT_SECRET: ${GOOGLE_OAUTH_CLIENT_SECRET}\n      TOKEN_ENC_KEY: ${TOKEN_ENC_KEY}\n      AUDIT_DB_PATH: /app/data/audit.db\n    volumes:\n      - ads_data:/app/data\nvolumes:\n  ads_data:\n```\n\nThen:\n\n- Point the LibreChat entry at `url: \"http://localhost:3001/mcp\"` with `requiresOAuth: true` (see [LibreChat](#add-it-to-your-ai-harness) above), and keep `localhost` in `mcpSettings.allowedDomains`.\n- Register `http://localhost:3001/auth/callback` as a redirect URI on the Google OAuth client.\n- Sign in from Chrome or Firefox: the consent cookie is `Secure`, and those treat `localhost` as a secure context. Safari may refuse it.\n- The sidecar shares the api container's network, so recreate both together after changes: `docker compose up -d --force-recreate api google-ads-mcp`.\n\nPublish the OAuth app to In Production so per-user tokens do not expire after 7 days.\n\n## Tools\n\nSeven tools, each takes an `action`:\n\n| Tool | Actions | What it covers |\n|------|---------|----------------|\n| `google_ads_account` | `status`, `list`, `switch`, `audit` | connection and token check, accounts you can reach, switch the active account, read the audit trail |\n| `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 |\n| `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 |\n| `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` |\n| `google_ads_keyword` | `list`, `add`, `update` | list with quality score, add positive or negative, change bid or status |\n| `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 |\n| `google_ads_lookup` | `location`, `language` | turn a location or language name into the numeric id that targeting and geo reports need |\n\nA 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.\n\n## Coverage\n\n| | |\n|---|---|\n| Google Ads API version | v24.1 (via [`google-ads-api`](https://github.com/Opteo/google-ads-api), Opteo) |\n| Coverage model | Curated actions for the common workflows, plus raw GAQL over every non-sensitive report resource. Not a 1:1 mapping of the ~100 API services. |\n| Write safety | Preview-first, yes/ok confirmation, PAUSED creates, durable audit log (see [Money and safety](#money-and-safety)). |\n\n## Big reports and pagination\n\nReports are bounded per response and paged without hitting Google again:\n\n1. 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.\n2. Leftover rows are parked in a short-lived (about 5 minutes), per-account cache.\n3. 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.\n\nOne 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.\n\n## Scheduled and unattended use\n\nReporting is read-only and retried, so it's reliable for cron and scheduled jobs once the OAuth app is In Production.\n\n- 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.\n- Call `google_ads_account` with `status` at the start of a routine to fail fast.\n- Quota and rate errors are retried with backoff, and if they still fail you get a clear \"needs Standard Access\" message.\n\n## Troubleshooting\n\n| Symptom | What to try |\n|---------|-------------|\n| Refresh token dies after ~7 days | OAuth app is still in **Testing**. Publish it to **In Production**. |\n| `PERMISSION_DENIED` / \"set login-customer-id\" | `GOOGLE_ADS_LOGIN_CUSTOMER_ID` is only for reaching a client *through a manager*. For an account you access directly (it shows as `[client]` in `account list`), leave it empty. Setting a manager you do not reach that client through, or setting one at all when none is needed, causes this error. If it is still denied, the login genuinely lacks access to that customer id. |\n| `unauthorized_client` on token exchange | The refresh token was minted by a different OAuth client than `GOOGLE_OAUTH_CLIENT_ID`. Re-mint it with that exact client (in the OAuth Playground, check \"Use your own OAuth credentials\" first). |\n| Quota / rate errors | Developer token may need **Standard Access**. Reads are retried with backoff. |\n| \"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\"`). |\n| \"not connected\" / reconnect message | Refresh token revoked; re-auth (HTTP) or mint a new refresh token (stdio). |\n| HTTP container exits on boot | Missing `TOKEN_ENC_KEY`, `PUBLIC_BASE_URL`, or `ALLOWED_REDIRECT_ORIGINS`. Check logs. |\n| OAuth works but LibreChat never connects | Set `requiresOAuth: true` and `startup: false` on the MCP entry, add the host to `allowedDomains`, and point `url` at the public `/mcp` path. |\n| LibreChat logs `OAuth not supported in unauthenticated discovery` or repeated `Reinspection failed ... timeout` | Missing `requiresOAuth: true`. LibreChat is probing the server without auth to list tools, which always fails on an OAuth server. The flag silences the probe and shows the sign-in prompt directly. |\n| Stale connection after changing the server URL under the same name | LibreChat caches OAuth tokens per server name. Reconnect, or clear its cached tokens for that server, so it registers fresh against the new backend. |\n| Google returns `redirect_uri_mismatch` mid sign-in | `PUBLIC_BASE_URL/auth/callback` is not registered on the OAuth client. For localhost that is `http://localhost:3001/auth/callback`. |\n\n## Notes\n\n- 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 v24.1. The high-level query and mutate calls used here are version-agnostic, but check method signatures if you upgrade the library.\n- Error messages and tool text are in English.\n- See [SECURITY.md](SECURITY.md) for disclosure, residual risk, and key-compromise playbook. See [CHANGELOG.md](CHANGELOG.md) for release notes.\n\n## Acknowledgments\n\nThis 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.\n\n## Disclaimer\n\nThis is an unofficial Google Ads API integration. It is not affiliated with, endorsed by, or supported by Google. \"Google Ads\" is a trademark of Google LLC. You are responsible for your account and any spend; use it within Google's API Terms of Service.\n\n## License\n\nMIT\n\n## Contributing\n\nSee [CONTRIBUTING.md](CONTRIBUTING.md).\n","readmeFilename":"README.md"}