v0.6.0
v0.6.0 — Shared negative keyword list lifecycle
Closes the loop on shared negative keyword lists. Before this release, propose_negative_keyword_list could create a new list, but once it existed there was no way to modify its contents through MCP — every change had to go through the Google Ads UI. The orchestration rules already told the AI to check for existing lists before creating new ones, but that check had no follow-up tool, so the AI would find a matching list and then create a duplicate anyway.
This release ships the three missing pieces so the full create → append → remove lifecycle works end-to-end through the same preview / dry-run / confirm safety layer as every other write tool.
New tools
add_to_negative_keyword_list(shared_set_id, keywords, match_type)— appends keywords to an existing SharedSet instead of forcing a new list. Collapses case-insensitive duplicates, rejects whitespace-only entries, and requires a numericshared_set_id. Returns a preview with aplan_idforconfirm_and_apply.remove_entitynow acceptsentity_type="shared_criterion"— removes a single keyword from a shared list. Theentity_idis the compositesharedSetId~criterionId; feed it straight from the newresource_idfield below. Still irreversible, still triggers the double-confirmation safety annotation.
Read-side helper
get_negative_keyword_list_keywordsnow returnsresource_idon every row ("sharedSetId~criterionId"). Callers pipe it intoremove_entitywithout hand-assembling composite IDs. Mirrors the existingresource_idpattern onget_negative_keywords.
Updated orchestration rules
The negative-keyword pattern in .cursor/rules/adloop.mdc (and the synced .claude/rules/adloop.md) now documents the three-way branch:
- Direct campaign negatives (
add_negative_keywords) — campaign-specific, no reuse. - Append to an existing shared list (
add_to_negative_keyword_list) — use whenever a matching list exists. Callget_negative_keyword_list_keywordsfirst to avoid duplicate-keyword errors at apply time. - Create a new shared list (
propose_negative_keyword_list) — only when no suitable list exists yet.
Plus the removal path: remove_entity with entity_type="shared_criterion" and the resource_id from the read tool.
Safety
- New append tool goes through the standard
ChangePlan→store_plan→confirm_and_applypipeline. No bypass. shared_criterionremoval inherits the existing"remove"match inrequires_double_confirmation, so both confirmations still fire.- Apply-time validation rejects a bare
entity_idforshared_criterionwith a clear error pointing at the correct format, rather than letting an opaque Google Ads API error surface.
Verification
- 135/135 unit tests passing (16 new).
- Two new behavioral evals cover: preferring
add_to_negative_keyword_listover recreating a duplicate list, and usingentity_type="shared_criterion"with an irreversibility warning for removal. - End-to-end verified against the live Google Ads API: append + remove round-trip on a real shared set, with
resource_idvisible on every row.
Also in this release
remove_entitydocstring insrc/adloop/ads/write.pynow enumerates every supportedentity_typeand its compositeentity_idformat. It had grown stale asshared_criterion,campaign_asset,asset, andcustomer_assetwere added over several releases.- Write evals re-ordered to 8 → 9 → 10 so the file scans in id order.
Install / upgrade
uv add adloop # or
pip install --upgrade adloopCredits
New tool design, implementation, tests, and live verification by @illia-sapryga in #15 — their first contribution to AdLoop. Welcome!
Full Changelog: v0.5.2...v0.6.0