gads-write-mcp
Provides guardrailed management of Google Ads accounts, with per-user OAuth authentication, role-based permissions, and planned capabilities for pausing or enabling campaigns, adjusting budgets and bids, managing keywords, and creating responsive search ads.
Click on "Deploy 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., "@gads-write-mcpPause the 'Summer Sale' campaign and show me the preview before applying."
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.
gads-write-mcp
A guardrailed write MCP server for Google Ads, for the marketing team to use from Claude web and desktop.
This server can spend real money. Every design decision here is downstream of that fact.
Separate repo, separate process, separate hostname (adswrite.indiraivf.in)
from the read-only googleads/google-ads-mcp already on this box. Google's
code is not forked or modified.
Current state: Phase 1 complete, awaiting verification
Phase 1 is the skeleton: config validation, Google OAuth, caller identity,
role resolution, and one health_check tool.
There is no Google Ads client in this phase. The google-ads library is
not even a dependency — it is added in Phase 3. Nothing in this codebase can
currently touch an ad account.
Phase | Scope | State |
1 | Skeleton: settings, OAuth, | built |
2 | Safety core against a fake executor, no API calls | not started |
3 | Reads: per-user client, accounts, performance, search terms | not started |
4 | First mutation ( | not started |
5 | Budget, bids, negatives, keywords, RSA | not started |
6 | Rollout: runbook, log shipping, alerting, rollback | not started |
Related MCP server: Google Ads MCP
How safety is layered
Five independent things must all agree before a single field changes. Any one of them says no, nothing happens.
Google's own permissions. Every Ads call uses the calling user's OAuth token. No static refresh token exists anywhere. Remove someone from the MCC and they lose access immediately — no redeploy, no config edit here.
The kill switch.
GADS_WRITE_ENABLED=falsemakes the whole server read-only. One env change, one restart.Role tier.
config/roles.yamlmaps email toreadonly/operator/lead. Unlisted people getnone.Managed accounts. The accounts this server may touch are derived from your MCC, not configured. That protects your developer token; it is not a spend control.
Parity with the Google Ads UI. You can do here what you could already do there. Very little is refused: only what cannot be undone (there are no delete tools), an account outside your MCC, or a change whose preview would misrepresent it. Risky-but-legitimate choices are warnings, not blocks. One relative backstop catches a stray digit.
Two-step confirm. Writes return a preview and a
plan_idand do nothing else. A separate call applies it, and a person must accept the preview first. This is the real control — everything above supports it.
Local setup
python3 -m venv .venv
.venv/bin/pip install -e '.[dev]' # Windows: .venv\Scripts\pip
cp .env.example .env # then fill it in
.venv/bin/python -m pytest # 15 tests, no network neededNote for the Windows dev box: ops/run.sh and ops/nginx.conf are for the
Ubuntu server. Locally you only need python -m gads_write.server, and even
that needs a .env with real OAuth credentials to be useful.
Configuration
Three files, and only these three, decide what this server can do:
File | Controls | Committed? |
| credentials, port, kill switch, the two spend settings | no |
| break-glass tier overrides — optional, normally absent | yes |
There is no policy file. Everything it used to hold is now derived or fixed:
Was in | Now |
| every account under |
| read from each account |
| removed — a rupee figure is wrong for some account, and always goes stale |
|
|
daily increase ceiling | removed as a limit — shown on the preview instead, so the person approving decides |
| now a warning on the preview, not a refusal |
|
|
structural rules, blocked ops, plan TTL | fixed in |
The point of that table: deploy it, add people in Google Ads, hand over the URL. Nobody edits a file to add an account or onboard a colleague.
No customer ID, budget limit, or email address may be hardcoded in Python.
The server validates all of this at boot and refuses to start if anything
is missing or malformed — reporting every problem at once, not one per
restart. Try it: unset GADS_DEVELOPER_TOKEN and start the server.
Deploying (Ubuntu EC2)
1. Google Cloud console
Create an OAuth 2.0 Web application client (or reuse the existing project — a new client, so this server's sessions are independent of the read-only one). Set:
Authorised JavaScript origin:
https://adswrite.indiraivf.inAuthorised redirect URI:
https://adswrite.indiraivf.in/auth/callback
Scopes requested are openid, userinfo.email, and adwords. The adwords
scope is requested from Phase 1 even though nothing uses it until Phase 3 —
adding a scope later forces every user to re-consent.
2. DNS and TLS
Point adswrite.indiraivf.in at the box, then:
sudo cp ops/nginx.conf /etc/nginx/sites-available/adswrite.indiraivf.in
sudo ln -s /etc/nginx/sites-available/adswrite.indiraivf.in /etc/nginx/sites-enabled/
sudo certbot --nginx -d adswrite.indiraivf.in
sudo nginx -t && sudo systemctl reload nginxThe Nginx config disables response buffering and raises the read timeout to 300s. Both are required: MCP streamable HTTP delivers server-sent events, and Nginx's defaults would buffer them and cut the connection at 60s.
3. Application
git clone <repo> /home/ubuntu/gads-write-mcp && cd $_
python3 -m venv .venv
.venv/bin/pip install -e .
cp .env.example .env && $EDITOR .env # keep GADS_WRITE_ENABLED=false
# set cwd in ops/ecosystem.config.js to this directory
pm2 start ops/ecosystem.config.js
pm2 save
pm2 logs gads-write-mcpPort 8081, so it does not collide with the read-only server on 8000.
instances: 1 is deliberate and must stay — Phase 2 holds draft plans in
process memory, and a second worker would not see plans drafted by the first.
4. Connect from Claude
Add a custom connector pointing at https://adswrite.indiraivf.in/mcp.
Phase 1 verification
Do these in order. Do not proceed to Phase 2 until all four pass.
Boot fails loudly on bad config. Comment out
GADS_DEVELOPER_TOKENin.env, runpm2 restart gads-write-mcp, checkpm2 logs. Expect a refusal listing the problem, not a running server. Put it back.You see yourself. Connect from Claude web, run
health_check. Expect your own email,tier: lead, andwrite_enabled: false.Someone else sees themselves. A second person connects and runs
health_check. Expect their email — not yours, not a shared identity. This is the one that actually proves per-user OAuth. If both of you see the same email, stop; something is badly wrong.An unlisted person is fenced out. Someone not in
config/roles.yamlconnects and runshealth_check. Expecttier: noneand the "ask a lead to add you" note.
Worth also checking in step 2: credentials.google_token_available should be
true. That proves the Phase 3 credential path already works, without the
token ever leaving auth/identity.py.
Where things live
config/ roles.yaml — break-glass overrides, normally absent
src/gads_write/
server.py FastMCP app, tool registration
settings.py boot validation, fails fast
auth/ identity.py (who), roles.py (what tier)
ads/ phase 3+ — executor.py is the ONLY mutate call site
safety/ phase 2 — guards, policy, plans, validators, audit
tools/ phase 3+ — one module per tool group
tests/
ops/ ecosystem.config.js, nginx.conf, run.shMoney is in micros
Google Ads expresses currency in millionths of the unit. ₹500/day is
500_000_000 micros. A missing conversion is a 1,000,000× error that
type-checks perfectly and passes review.
Rule: every variable holding money carries its unit in the name —
budget_micros, budget_rupees — and conversion happens in exactly one
place. Never a bare budget.
This server cannot be deployed
Maintenance
Related MCP Connectors
Connect authorized ad accounts to AI assistants for audits, reporting, and approval-gated changes.
Run Google Ads and Meta Ads from ChatGPT or Claude: audit wasted spend, create and manage campaigns.
- AdLoopOAuthcom.getadloop
Google Ads, GA4 and Tag Manager in your AI client, with a preview before every change.
Related MCP Servers
- FlicenseNot gradedqualityCmaintenanceEnables comprehensive management of Google Ads campaigns through natural language, including campaign creation, ad group management, keyword operations, Performance Max campaigns, conversion tracking, and performance insights with support for multiple accounts.4-
- AlicenseAqualityDmaintenanceEnables managing Google Ads campaigns through an AI assistant with read-only reporting, recommendations, and gated write operations for bids, budgets, and statuses, all backed by preview and audit logging.311MIT
- AlicenseNot gradedqualityAmaintenanceA Google Ads MCP server that enables safe, auditable management of ad accounts through natural language, including proposing, reviewing, applying, and rolling back changes with guardrails and dry-runs.MIT
- AlicenseBqualityAmaintenanceEnables reading and analyzing Google Ads data, planning keywords, and previewing campaign changes before applying them, all through natural language.31248 npmApache 2.0