A Google Ads Codex workflow becomes risky at the exact moment the agent can act. The safe first run should not “optimize” anything. It should prove that it has the right account, the right date range, and enough evidence to make a recommendation.
That sounds conservative. It is also the fastest way to find whether the integration is trustworthy.
The implementation pattern I would use is simple:
- bind the agent to one explicit account;
- read a bounded slice of data;
- return evidence in a predictable shape;
- propose changes without applying them;
- require confirmation for every write; and
- read the platform again to verify any approved change.
This article shows what that loop looks like in practice.
Start with a read-only contract
Google’s current Google Ads MCP server is strictly read-only. It can list accessible customers, inspect resource metadata, and run Google Ads Query Language searches. That makes it a useful reference point even if the integration you eventually use also supports writes.
For the first run, pass a contract that removes as much interpretation as possible:
{
"account_id": "CUSTOMER_ID",
"window": {
"start": "YYYY-MM-DD",
"end": "YYYY-MM-DD"
},
"grain": "campaign",
"metrics": [
"cost",
"clicks",
"conversions",
"conversion_value"
],
"mode": "read_only"
}
The important field is not mode. It is account_id.
An advertising login can expose a manager account, several client accounts, or both. If the agent silently chooses one, every conclusion after that point can be well-formed and still be wrong. The run should stop when the account is missing or ambiguous.
The same rule applies to dates. “Recently” is not a data contract. Use an exact start date, exact end date, and the account’s reporting timezone.
Separate observation from judgment
Do not let the agent jump from a query result to a command. Make it produce an evidence object first.
{
"account_id": "CUSTOMER_ID",
"window": "YYYY-MM-DD..YYYY-MM-DD",
"observations": [
{
"entity_type": "campaign",
"entity_id": "CAMPAIGN_ID",
"finding": "Spend increased while recorded conversions fell",
"support": {
"cost_change_pct": 18.4,
"conversion_change_pct": -11.2
}
}
],
"blocked_by": []
}
This boundary catches several common failures:
- the selected window has no delivery;
- conversion tracking changed inside the window;
- money values were compared without checking currency;
- a paginated result was treated as the complete account; or
- a campaign was evaluated without its status or budget context.
When one of those conditions is present, blocked_by should explain it. The agent should not fill the gap with a guess.
Make proposals executable, but not automatic
A useful proposal is specific enough to apply later and clear enough to reject now.
{
"action": "pause_campaign",
"entity_id": "CAMPAIGN_ID",
"reason": "Cost increased and recorded conversions declined in the bounded comparison",
"evidence_refs": ["observation-1"],
"expected_effect": "Stop additional delivery while tracking is checked",
"reversal": "enable_campaign",
"requires_confirmation": true
}
Three details matter here.
First, the proposal refers to evidence returned by the read step. Second, it describes a reversal. Third, confirmation is data, not a vague sentence in a system prompt.
This makes the boundary testable. A tool wrapper can reject any mutation when requires_confirmation is absent, false, or not paired with a fresh human approval.
Put the write gate outside the prompt
Prompts are useful instructions, but they are a poor permission system. A production workflow should enforce the boundary in the tool layer as well.
At minimum:
- expose read tools during the first run;
- require a separate confirmation token or approval event for a mutation;
- allow only the proposed entity and operation;
- expire approval after a short interval;
- reject retries when the previous result is unknown; and
- record the request, response, and resulting platform state.
Budget increases, deletes, new campaign enablement, audience expansion, and bulk edits deserve their own tighter gates. A generic “apply recommendations” permission is too broad.
Verify the result with a second read
A successful tool response is not proof that the requested state is now visible in Google Ads.
After an approved write, query the exact entity again and compare the returned state with the requested state. The verification record should contain:
- the account ID;
- the entity ID;
- the requested change;
- the platform state returned after the change;
- the timestamp; and
- any mismatch or partial failure.
If the read-back does not match, stop. Do not retry a mutation automatically. An unclear result is a state-reconciliation problem, not an invitation to click twice.
A first prompt worth testing
Once the connection works, I would start with a prompt like this:
Use read-only Google Ads tools only.
1. List the accessible accounts and stop if more than one plausible account exists.
2. After I provide the account ID, query the exact 28-day date range I specify.
3. Return campaign status, cost, clicks, conversions, and conversion value.
4. State any missing data, tracking caveat, currency issue, or incomplete result.
5. Propose no more than three actions, each tied to returned evidence.
Do not change budgets, bids, targeting, assets, campaign status, or account settings.
Do not retry a failed or uncertain operation.
That prompt will not produce a dramatic demo. It will show whether the agent respects scope, reports uncertainty, and keeps recommendations separate from actions.
Applying the pattern in Codex
Codex plugins can bundle skills and connectors, and the current install flow uses the plugin browser in Codex. The important design choice is still the same after installation: begin with account discovery and bounded reads, not a write-enabled optimization request.
For one concrete implementation, the current Adspirer Codex setup guide documents a plugin that connects Codex to Google Ads and other advertising platforms. The install begins by adding its marketplace:
codex plugin marketplace add amekala/ads-mcp
Then open /plugins, install Adspirer Ads Agent, and start a new Codex session. Its documented safety behavior includes read-before-write checks, paused campaign creation, and confirmation before budget changes.
Those defaults are useful, but they do not replace an operator’s own gates. The prompt, tool permissions, proposal schema, and read-back verification should all point in the same direction.
Test the failures before the happy path
Before trusting the workflow, run it against cases that should stop:
- two accessible accounts with similar names;
- an exact date range with no delivery;
- an account whose currency differs from the expected currency;
- a response with an incomplete page of campaigns;
- expired or insufficient permissions;
- a proposal based on stale data; and
- a write whose outcome cannot be confirmed.
The pass condition is not “the agent produced an answer.” The pass condition is that it stopped at the correct boundary and explained why.
A safe ads agent is intentionally boring at first: read, diagnose, propose, approve, apply, verify. Once that sequence is reliable, richer automation becomes an engineering decision instead of a leap of faith.
Affiliation disclosure: This article was prepared for Adspirer. Adspirer’s Codex integration is used as one concrete implementation example.
Top comments (0)