@metaharness/example-ads
v0.2.0
Published
MetaHarness example — Google Ads + Meta Marketing API harness, campaign/spend analysis read-only by default, mutations gated (npx @metaharness/example-ads my-bot)
Downloads
324
Maintainers
Readme
@metaharness/example-ads
Multi-agent scaffold for Google Ads + Meta Marketing API — campaign and spend analysis, read-only by default, mutations gated.
⚠️ Illustrative output. Transcripts, metric values, and validation output shown in this README are representative examples, not captured from a specific run. Actual output depends on your environment, models, credentials, and ad account data. Run the commands to see real results.
⚠️ Not for production advertising operations. This scaffold is illustrative and educational. It is NOT certified for compliance with GDPR, CCPA, Google Ads API Terms of Service, Meta Platform Terms, or any other advertising regulation. All mutations are the sole responsibility of the operator. Always test with a sandbox / test account before connecting production credentials.
What it is
@metaharness/example-ads is a one-command scaffold that wires a three-agent
harness to Google Ads (google-ads-api, community library by Opteo) and
Meta Marketing API (facebook-nodejs-business-sdk v25, official Meta SDK).
Out of the box it can only read — campaign lists, spend breakdowns, performance
metrics, cross-channel summaries. Any write operation (budget change, campaign
pause, ad creation) requires an explicit environment-variable opt-in and is
still run through a validate_only dry-run before the live call.
What it is NOT
- Not a production advertising management system.
- Not a replacement for native ad managers (Google Ads UI, Meta Ads Manager).
- Not GDPR / CCPA compliant out of the box.
- Not affiliated with or endorsed by Google or Meta.
google-ads-apiis a community library — Google has no official Node.js client. This scaffold makes that clear in generated code comments.
Features
| Capability | Detail |
|---|---|
| Campaign spend analysis | GAQL query for metrics.cost_micros, metrics.impressions, metrics.clicks, metrics.conversions at campaign / ad-group level for any date range |
| Meta Insights fan-out | AdAccount.getInsights() at campaign, adset, or ad level; fields: spend, impressions, clicks, cpc, cpm, reach, roas |
| Cross-channel summary | Planner agent synthesises Google + Meta results into a unified spend dashboard |
| Budget-pacing alert | Detects campaigns on pace to over- or under-spend their daily/lifetime budget |
| Tiered model routing | Tier 2 (cheap/fast) for fan-out data extraction; Tier 3 (frontier) for cross-channel synthesis and anomaly narrative |
| MCP default-deny | .harness/mcp-policy.json grants only the minimum read tools; all mutation tools blocked unless ADS_MUTATIONS_ENABLED=true |
| /ads-analyze slash command | Single entry-point for analysis across both platforms |
| Three specialised agents | ads-planner (Tier 3), ads-fetcher (Tier 2), ads-verifier (Tier 2) |
| Verification gate | ads-verifier re-reads a random sample of metrics independently before the planner surfaces results |
| All hosts | --host flag scaffolds for claude-code, codex, copilot, github-actions, hermes, openclaw, opencode, pi-dev, rvm; --host all emits every config |
| Audit log | Every API call written to .harness/audit.jsonl |
Quickstart
npx @metaharness/example-ads@latest my-ads-bot
cd my-ads-bot
npm install
npm run doctornpm run doctor checks that required environment variables are set, that both
SDKs are reachable, and that the MCP policy file is intact.
To scaffold for a different host:
npx @metaharness/example-ads@latest my-ads-bot --host opencode
# or emit every host config at once:
npx @metaharness/example-ads@latest my-ads-bot --host allConfiguration
Google Ads credentials
| Env var | Where to get it | Required |
|---|---|---|
| GOOGLE_ADS_CLIENT_ID | Google Cloud Console → APIs & Services → Credentials → OAuth 2.0 Client | Yes |
| GOOGLE_ADS_CLIENT_SECRET | Same OAuth 2.0 client entry | Yes |
| GOOGLE_ADS_DEVELOPER_TOKEN | Google Ads Manager account → Tools → API Center | Yes |
| GOOGLE_ADS_REFRESH_TOKEN | Run the OAuth flow; npm run get-google-token in the scaffolded project prints instructions | Yes |
| GOOGLE_ADS_CUSTOMER_ID | Your Google Ads account ID, no hyphens (e.g. 1234567890) | Yes |
| GOOGLE_ADS_LOGIN_CUSTOMER_ID | Manager account ID — required only when accessing sub-accounts via a manager | Conditional |
Meta Marketing API credentials
| Env var | Where to get it | Required |
|---|---|---|
| META_APP_ID | Meta for Developers → My Apps → your app → Settings → Basic | Yes |
| META_APP_SECRET | Same page | Yes |
| META_ACCESS_TOKEN | Graph API Explorer for testing; System User token for production | Yes |
| META_AD_ACCOUNT_ID | Meta Business Manager → Ad Accounts; format: act_XXXXXXXXXX | Yes |
Store credentials in a .env file (gitignored by the scaffold) or your
preferred secrets manager. Never commit credentials to source control.
Sandbox / test mode
Google Ads — test accounts (recommended for development):
Create a test manager account at ads.google.com/home/tools/manager-accounts/
and use the same developer token as your production account. Test accounts
cannot interact with production accounts and do not serve ads or accumulate
spend. Note: test accounts return empty serving metrics (impressions, cost,
conversions are zero); the verifier agent is aware of this and will not flag
zeroed metrics as discrepancies when GOOGLE_ADS_TEST_MODE=true is set.
GOOGLE_ADS_TEST_MODE=true # set to acknowledge zero-metric test-account behaviourMeta Marketing API — Sandbox Ad Accounts:
In Meta for Developers → your app →
Marketing API → Tools → Sandbox Mode, create a Sandbox Ad Account. Set
META_AD_ACCOUNT_ID to the sandbox account ID (act_XXXXXXXXXX). Sandbox
accounts accept all read/write API calls but never deliver ads or accumulate
spend. As of 2026, sandbox accounts do not return synthetic insights data —
plan to use a real (low-budget) account for end-to-end insights testing.
Mutation gate:
By default, ADS_MUTATIONS_ENABLED is unset, which means the MCP policy
blocks all write tools at the harness boundary. To enable mutations after
completing your sandbox testing:
ADS_MUTATIONS_ENABLED=true # enables budget updates, campaign pause/enableEven with mutations enabled, all mutation calls go through validate_only
first. The verifier agent confirms the mutation landed before reporting success.
Usage
Slash command
/ads-analyze [--days N] [--level campaign|adset|ad] [--platform google|meta|both]--days— lookback window in days (default7)--level— granularity (defaultcampaign)--platform— which platform(s) to query (defaultboth)
Representative prompts
/ads-analyze --days 30 --level campaignThe planner will fan out fetchers for each platform, collect campaign-level metrics for the last 30 days, run the verifier spot-check, then surface a cross-channel spend summary.
Show me which campaigns are under-pacing their daily budget as of today.The planner interprets this as an analysis task, dispatches the fetcher with
--days 1 --level campaign, and applies budget-pacing logic before the
verifier confirms the figures.
What was our blended CPC across Google and Meta last week?Cross-channel metrics query; the planner normalises Google's cost_micros /
clicks with Meta's cpc field before presenting a unified answer.
With mutations enabled (ADS_MUTATIONS_ENABLED=true):
Pause all Google Ads campaigns with a 7-day CPA above $50.The planner identifies candidates via a read query, runs validate_only
mutations on each, presents the dry-run results for confirmation, then — only
after the operator confirms — executes the live mutations. The verifier
re-reads each campaign's status to confirm PAUSED landed.
Safety
| Control | Default | Override |
|---|---|---|
| Mutations blocked by MCP policy | Blocked (default-deny) | ADS_MUTATIONS_ENABLED=true |
| Google Ads validate_only pre-check | Always on for any mutation path | Not bypassable from prompts |
| Google Ads test account | Recommended for dev | Set GOOGLE_ADS_CUSTOMER_ID to test account ID |
| Meta Sandbox Ad Account | Recommended for dev | Set META_AD_ACCOUNT_ID to sandbox act_ ID |
| Verification gate (re-read before "done") | Always on | Not bypassable from prompts |
| Audit log | Always on (.harness/audit.jsonl) | Not bypassable from prompts |
| Secrets in env only | Enforced by scaffold | Never written to scaffolded files |
The audit log records every API call (read or attempted mutation) with timestamp, tool name, arguments hash, and response status. It is append-only and not cleared by the harness.
How it works
Agent pipeline
Operator prompt
│
▼
ads-planner (Tier 3 — frontier model)
Interprets intent; decides accounts, date range, metrics, level.
Dispatches ads-fetcher in parallel fan-out.
│
▼
ads-fetcher × N (Tier 2 — cheap/fast model)
One claim per (platform × account).
Google: customer.report() with GAQL.
Meta: AdAccount('act_...').getInsights([...fields], {date_preset}).
Returns raw JSON rows.
│
▼
Tier 1 WASM booster (no LLM)
Normalises cost_micros → spend_usd, unifies field names,
deduplicates pagination cursors.
│
▼
ads-verifier (Tier 2 — cheap/fast model)
Re-reads a random 10% sample via independent API calls.
Compares figures; flags if delta > 1%.
If GOOGLE_ADS_TEST_MODE=true, accepts zero metrics as valid.
On mutation paths: re-reads the mutated resource to confirm change landed.
│
▼
ads-planner (Tier 3 — frontier model)
Synthesises cross-channel summary.
Applies budget-pacing logic.
Surfaces final answer only after verifier clears.MCP policy — granted tools
The .harness/mcp-policy.json grants exactly these tools by default:
ads.google.report— GAQL report query (read)ads.google.query— direct GAQL query (read)ads.meta.getInsights— Insights API (read)ads.meta.getCampaigns— campaign list (read)ads.meta.getAdSets— ad-set list (read)ads.google.validate_mutate— dry-run validation only (never live)audit.log— all calls logged
Mutation tools (ads.google.mutate, ads.meta.createCampaign,
ads.meta.updateCampaign) are listed in mutation_grants and are
hard-blocked unless ADS_MUTATIONS_ENABLED=true is present in the
environment. Denied calls are hard errors with audit-log entries, not
silent drops.
Routing tiers
| Tier | What runs there | Model class |
|---|---|---|
| Tier 1 (WASM, no LLM) | cost_micros → spend_usd normalisation; field unification | None |
| Tier 2 (cheap) | ads-fetcher fan-out; ads-verifier spot-checks; pagination cursor walking | Haiku-class |
| Tier 3 (frontier) | ads-planner synthesis; anomaly narrative; budget-pacing analysis | Sonnet/Opus-class |
Links
- google-ads-api on npm (community library by Opteo)
- google-ads-api on GitHub
- Google Ads API official docs
- Google Ads API test accounts
- Google Ads API testing best practices
- facebook-nodejs-business-sdk on npm
- facebook-nodejs-business-sdk on GitHub
- Meta Marketing API docs
- Meta Marketing API Sandbox Ad Accounts
- ADR-064 — this design record
- ADR-051 — examples program contract
License
MIT. Built on metaharness.
