Skip to content
quickstart

From a clean clone to a connected agent

This is a pnpm workspace. Install and build the connector packages, start the control plane, connect the platforms you use, give the tenant its brand context, then point any MCP-compatible agent at the server. Everything below is grounded in what the code does today.

1 · Install and build

Install once at the workspace root, then build the contract package that every connector consumes through its dist/ types.

$ pnpm install
$ pnpm --filter @advertisingagents/connector-core build
# or build every connector: pnpm -r --filter "@advertisingagents/connector-*" build

2 · Connect your platforms

Copy the example env file and fill in only the platforms you use. Start the control plane, then run each platform’s OAuth flow from the console or over REST. Tokens are stored locally, encrypted at rest, and never pass through AdvertisingOS.

$ cp apps/api/.env.example apps/api/.env
$ pnpm dev:api # control plane REST on :2997
# mint an authorization URL (or use the console at :2998)
$ curl -s -X POST localhost:2997/connections/meta/authorize-url
✓ connection stored after you approve in the browser

3 · Connect your agent

The central MCP server speaks stdio and exposes the same service layer as the REST API. Register it with any MCP client — Claude Code, Claude Desktop, or your own framework. Hosted access is Streamable HTTP with OAuth at /mcp.

$ claude mcp add advertising.sh -- pnpm --filter @advertisingagents/api mcp
# AdvertisingOS MCP server ready on stdio
# claude_desktop_config.json
{ "mcpServers": { "advertising-sh": {
  "command": "pnpm", "args": ["--filter", "@advertisingagents/api", "mcp"] } } }

4 · Onboard the brand from its website

Hand over the website (and, if connected, the ad account). The research drafts the brand context — voice, products, inferred personas, value props, claim candidates with their quotes, strategy hypotheses — and stores nothing until you apply what you approve. Fill in by hand only what the site does not say. From then on generate_creative writes inside it and returns a brandCheck, and check_creative reads any copy against it — a lexical pass over your own lists, never a platform’s policy ruling.

# research_brand — fetch the site, read the account, draft the context
{ "website": "https://oatco.example", "connectionId": "c_meta_1", "accountId": "act_123" }
✓ run brr_… ready — 5 sources · 2 inferred personas · 7 claim candidates · 3 strategy hypotheses
# apply_brand_research — sections by name, claims only by id
{ "id": "brr_…", "accept": { "voice": true, "personas": true, "claimIds": ["foams-like-dairy-1"] } }
# set_brand_context — for anything the site does not say; each section given replaces the stored one
{ "voice": { "summary": "Plainspoken, warm, specific.", "avoidedTerms": ["synergy"] },
  "approvedClaims": [{ "id": "foam", "text": "Foams like dairy", "evidence": "cupping test 2026-07" }],
  "blockedTerms": ["guaranteed", "#1"] }
✓ context v1 stored — every later check reports the version it ran against

The rest of the record — the change log, the observed before/after impact readings and the learnings ledger — fills itself as you work. The ledger refuses attribution language in code: an observation that says a change caused anything comes back 400, naming the word.

Standard tools

Every connector sits behind the same 145-tool surface — one normalized schema; capability differences are declared, never silent. Platform quirks are absorbed by the connector, not your agent. Mutations default to dry_run — they validate and report, spending nothing until a human approves a live call, which must carry the confirmToken the dry-run preview minted. Families, in the order the server lists them: connections · campaigns · objects · insights · flights · pacing · alerts · platform-calls · optimization · audiences · mmm · creative · exports · context · launches · plays · research · billing · approvals.

list_platformsEvery supported connector and whether its credentials are configured.read
list_connectionsStored platform connections (id, platform, label, status). Never tokens.read
list_accountsAd accounts reachable through a connection, with ids and currencies.read
set_platform_credentialsStore this tenant's own OAuth app credentials for a platform (client id/secret + extras); sealed in the vault, write-only.write
list_platform_credentialsPlatforms where this tenant has stored its own OAuth app — client id masked, never secret material.read
clear_platform_credentialsRemove this tenant's stored OAuth app for a platform; its OAuth flows fall back to the deployment's shared credentials.write
get_audit_logEvery connector action — actor, mode, outcome, duration — newest first.read
list_campaignsCampaigns across connections; fans out with per-target partial errors.read
get_campaignA single campaign by connection, account, and campaign id.read
update_campaignChange a campaign's name, status, budget, or schedule. Defaults to dry_run.write
pause_campaignPause a running campaign. Defaults to dry_run.write
resume_campaignResume a paused campaign. Defaults to dry_run.write
set_statusSet a campaign active or paused through the governed modify path.write
update_budgetChange a campaign's budget (integer micros). Defaults to dry_run.write
archive_campaignMove a campaign to the platform's recoverable archive state, where it has one. Defaults to dry_run.write
delete_campaignIrreversibly remove a campaign in the platform's own meaning (delete, mark REMOVED, archive terminally). Super admins only. Defaults to dry_run.write
get_campaign_pacingPacing for a campaign that carries its own commitment — lifetime budget plus both schedule dates. Refused otherwise, with the fix named.read
list_objectsCampaigns, ad sets, or ads under an account; parent-scoped.read
get_objectOne campaign, ad set, or ad, normalized to the unified model.read
modify_objectThe canonical edit for any ad object, with a before→after diff preview. Defaults to dry_run.write
set_line_budgetSet a line's budget at every level delivery enforces — campaign, ad set and its ads — under one confirmation. Defaults to dry_run.write
replace_ad_creativeDuplicate-and-swap creative replacement (Meta). Defaults to dry_run.write
get_ad_insightsAd-level metrics for creative insights — spend, CTR, frequency, actions per ad. Reads only.read
get_change_logApplied changes with structured diffs and performance snapshots.read
get_change_impactWhat was observed after each applied change — baseline at change time vs. the delivery that followed.read
get_impact_summaryThose readings rolled up by change family and actor, with what was confounded.read
rollback_changeInvert a recorded change through the same governed path. Defaults to dry_run.write
get_reportNormalized report over any date range — spend, ctr, cpa, roas — every platform, one shape.read
get_metrics_certificationWhich stored days are certified, provisional, or uncovered — a day inside its restatement window is never final, and an unsynced day is never zero.read
get_breakdownsThe same report sliced by publisher, market, store location or state — attribution grains, filter + rollup by city.read
get_digital_insightsThe site side: traffic and on-site commerce by property, page, brand, category or visitor geo — no spend, never summed.read
get_inventory_insightsThe supply side: avails, fill rate and remaining impressions by account, publisher, market, store, creative type or environment.read
list_flightsCross-platform campaign groupings with their linked targets.read
get_flightOne flight; optionally hydrated with live campaign state and spend.read
create_flightCreate a cross-platform flight grouping (control-plane only).write
update_flightRename a flight or adjust its planning window.write
delete_flightRemove a flight grouping; platform campaigns are untouched.write
add_flight_targetLink an existing platform campaign into a flight.write
remove_flight_targetUnlink a campaign from a flight.write
create_flight_lineAdd a sold line (its own budget and window) to a flight.write
update_flight_lineChange a flight line's sold budget, window or terms.write
delete_flight_lineRemove a sold line; its campaigns stay in the flight, unassigned.write
set_flight_target_lineAssign a flight campaign to one of the flight's lines, or unassign it.write
create_flight_lines_from_ad_setsOne anchored line per ad set of a flight campaign, defaulted from the ad set; what cannot be defaulted is skipped and reported, never guessed.write
flight_actionOne patch applied to every campaign in a flight, each governed. Defaults to dry_run.write
validate_flight_importNormalize a flight sheet (creates + edits), resolve each campaign, and mint a confirmToken bound to the exact set. Writes nothing.read
apply_flight_importCreate and edit flights from a validated sheet; requires the validate token. Control-plane only — no ad platform is touched.write
apply_import_budget_changesApply a flight sheet's staged campaign budget changes on the ad platforms, each governed. Defaults to dry_run.write
export_flightsThe flight store as import rows — download, edit, re-upload through validate_flight_import. No platform call.read
get_flight_pacingSpend vs committed plan: run rate, straight-line projection, pace index, required rate, policy-clamped proposals. Reads only.read
pace_flightExecute the pacing engine's clamped proposals through the governed modify path. Defaults to dry_run.write
get_pacing_portfolioEvery flight ranked by dollars at stake at landing, from stored snapshots. No platform call — each row carries its own freshness stamp.read
get_pacing_coverageWhich spending campaigns sit inside no active flight — covered, expired, or missing — from the store alone. No platform call.read
capture_pace_snapshotsRun the pacing engine and store the result. Reads platforms; writes nothing to them, so there is no dry_run ladder.write
get_pacing_historyPlan vs delivery for one flight from stored snapshots, plus the pace runs along the way. Nothing is recomputed.read
list_pace_alertsPacing alerts with the operator rule that fired each one, verbatim. Alerts never act.read
set_pace_alert_rulesSet the pacing alert thresholds. Each save bumps the rule version; alerts already fired keep their sentence.write
list_alertsThe tenant's KPI alert rules — each an operator threshold on a delivery KPI. Reads only.read
set_alert_ruleCreate or update a KPI alert threshold on a delivery metric, for all campaigns, one flight, or one campaign. Control-plane only — alerts notify, never act.write
delete_alert_ruleDelete a KPI alert rule; its fired/resolved event history is retained. Control-plane only.write
get_alert_historyFired and resolved KPI alert events, newest first — each names the rule, the reading, and the threshold it crossed.read
get_inboxEverything that needs a person, in one list — open pace alerts, fired KPI alerts, open recommendations and pending change requests, ranked needs-you first. Reads only.read
get_platform_callsThe outbound call log: one row per HTTP attempt against a platform API, so a 429 that succeeded on retry is still visible. No URLs, no credentials.read
get_platform_call_healthCalls, errors, 429s and nearest-rank p50/p95 per platform and endpoint. Measurements — no latency target, no benchmark.read
get_rate_limitsOur outbound budget for each platform and the live token-bucket state behind it. Our own choice of how hard to push, never the platform's published limit.read
set_rate_limitChange one platform's outbound budget. Control-plane config — contacts no platform, applies deployment-wide, takes effect on the next call.write
get_circuit_breakersWhich platforms we have stopped calling after they started failing, and when each next admits a single probe. Our own thresholds, never a claim that a platform is down.read
set_circuit_breakerTune when we give up on one platform — failure rate, window, slow-call cutoff, open period. Control-plane config; contacts no platform.write
close_circuit_breakerForce an open breaker to probe now, for an operator who knows the platform recovered. A probe, not a recovery — if it is still down it reopens.write
scan_anomaliesSpend spikes, budget overruns, conversion collapses vs a trailing baseline. Reads only.read
generate_recommendationsObjective-aware optimization heuristics over recent metrics. Reads only — spends nothing.write
list_recommendationsOptimization recommendations, newest first, optionally by status.read
approve_recommendationRun a recommendation's change as a dry run and mark it approved. Does not spend.write
apply_recommendationExecute an approved recommendation live — a real spend change. Human sign-off only.write
reject_recommendationReject a recommendation so it is no longer actionable.write
get_policiesGuardrail policies (budget caps, total spend cap) and this workspace's own destination policies for AI-surface campaigns.read
set_policyUpsert a guardrail policy, enforced on every live campaign write.write
list_audiencesCustom audiences across connections; unsupported connectors are skipped.read
create_audienceCreate an empty custom audience. Defaults to dry_run.write
update_audience_membersAdd or remove members; raw identifiers are SHA-256 hashed before any platform call.write
plan_audiencesPropose audiences for a plain-language brief — each with why it fits (graded, cited evidence) and which platforms can reach it; a read.read
get_media_mixSpend-band marginal-return analysis from the account's own history, with headroom-bounded mix suggestions. Reads only.read
generate_creativeDraft platform-tailored creative units from an intent brief. Copy only, no metrics; touches no platform.read
create_creative_planSave a brief + creative units + multivariate test matrix against a campaign. Plan surface — no ads created. Defaults to dry_run.write
list_creative_plansSaved creative plans, newest first, by connection, account, or campaign.read
get_export_destinationThe tenant's S3/MinIO export destination — masked credentials, never key material.read
set_export_destinationConfigure the object-storage export destination. Secret sealed in the vault, write-only.write
check_export_destinationProbe write+delete under the prefix — verifies bucket, region, and credentials without exporting.write
run_exportExport daily campaign metrics to the bucket, partitioned platform/yyyy/mm/dd. Uncovered spans are named, never zero-filled.write
list_export_runsExport run history — the coverage record for every bucket partition.read
get_brand_contextThe tenant's brand context — voice, products, offers, personas, approved claims, blocked terms, required disclosures. Null when none is set.read
set_brand_contextReplace one or more sections of the tenant's brand context (voice, values, positioning, ideal customer, personas, messaging, products, offers, latest updates, approved claims, blocked terms, disclosures, strategies). Control-plane only.write
check_creativeCheck creative units against the tenant's brand context and the platform's field limits — blocked terms, unapproved claims, avoided terms, missing disclosures, over-limit fields. Lexical heuristics; reads only.read
list_learningsThe tenant's learnings ledger — what was observed by persona, product, angle, channel, platform or campaign — newest first. Reads only.read
record_learningRecord what was observed about a persona, product, angle, channel, platform or campaign, with the tool and window it was read from. Control-plane only.write
retire_learningRetire a learning so planners stop reading it; the entry is kept with who retired it and when. Control-plane only.write
research_brandOnboard an advertiser: fetch its website (and read its connected ad account), then draft the brand context — voice, products, offers, inferred personas and ideal customer, value props, messaging, claim candidates, disclosures and strategy hypotheses — each with its source. Nothing is stored until applied.write
get_brand_researchA brand research run by id, or the tenant's latest — the sources fetched, the draft, and whether it was applied. Reads only.read
apply_brand_researchTake the operator's choices from a research draft into the brand context — sections by name, claims only by id — merged by id into what is already stored. Control-plane only.write
launch_campaignBuild a complete campaign on one platform from a platform-neutral draft — everything created paused; one confirm token for the whole launch.write
get_launchOne Launchpad run: state, plan, every step's outcome and the platform ids it created.read
list_launchesThis workspace's recent Launchpad runs, newest first.read
retry_launchResume a failed Launchpad run where it stopped; done steps are never created twice.write
reconcile_launchSettle one launch step whose outcome was unknown from what you found on the platform; writes the ledger only.write
list_launch_optionsChoices for a Launchpad picker (pages, advertisers, saved audiences, loaded locations); a platform read.read
list_product_facetsWhat is left in a brand's product catalog under the picks so far — categories, product lines, places, stores; a platform read.read
list_launch_destinationsEvery account this workspace could launch on, grouped by connection, each with its status and the platform's launch capability.read
create_launch_draftSave an in-progress (possibly partial) Launchpad draft to resume later; creates nothing on a platform.write
update_launch_draftAutosave a Launchpad draft; refused with the current version when it changed since baseUpdatedAt, unless force.write
get_launch_draftOne saved Launchpad draft: its (possibly partial) draft, destination, wizard step and updatedAt.read
list_launch_draftsThis workspace's saved Launchpad drafts, most recently edited first.read
delete_launch_draftDelete a saved Launchpad draft; control-plane state only.write
import_assetFetch an image or video the advertiser already hosts at a public https URL into the asset store; returns a ready asset id for a launch.write
list_play_templatesThe Plays you can switch on — each one's signal, what it prepares, its settings and whether it is available or a preview.read
list_playsThis workspace's Plays — on or off, what each watches, when it last checked and fired, and proposals waiting.read
get_playOne Play: its template, settings, scope, on/off, last check and proposals waiting.read
create_playSwitch on a Play from a template: pick what it watches and its settings; it prepares proposals, never spends.write
update_playSwitch a Play on or off, rename it, or change its settings or scope; refused with the current Play when it moved.write
archive_playSwitch a Play off for good: kept with its proposals, never deleted; its pending proposals expire.write
check_playRun a Play's check now: reads only; each new firing is stored as an Inbox proposal (deduped).write
list_play_proposalsWhat the Plays prepared — newest first, by Play or status.read
get_play_proposalOne proposal: the signal, its evidence, the prepared response and its status.read
approve_play_proposalPreview a proposal from fresh state (a fresh confirm token), then run it live through the prepared operation. Defaults to dry_run.write
dismiss_play_proposalSay no to a proposal; the same signal is never proposed again.write
list_competitorsThe competitors this account tracks — proposed, confirmed and retired (kept, never deleted) — with where each came from and why. Reads only.read
add_competitorAdd a competitor by name and, optionally, its website. Names and domains are unique per account, case-insensitively. Control-plane only.write
update_competitorRename a competitor, change or clear its domain, edit why, or set its status (retire keeps the row). Control-plane only.write
list_research_sourcesThe public pages this account named for research besides each site — retailer listings, company social pages, press, app-store pages. Reads only.read
set_research_sourcesReplace the whole set of operator-named research pages (≤50). Each must be a public https page and never a person's profile. Control-plane only.write
list_research_observationsDated facts read from the brand's and competitors' public pages — prices, offers, claims, positioning, review themes — newest first, each with its page, quote and staleness. Reads only.read
research_competitorsRead each competitor's public site (and the brand's, and every named source), record what changed as dated observations, and propose discoveries to keep or dismiss. Deterministic extraction; no ad platform is contacted.write
list_discoveriesWhat research proposed for the operator to keep or dismiss — competitor changes, claim candidates, possible competitors, persona evidence — newest first. Reads only.read
resolve_discoveryKeep a pending discovery (applied through the same path as a manual edit) or dismiss it so research never proposes it again. Control-plane only.write
list_prospect_listsThe account's own prospect lists with per-list counts (companies by research status, contacts). Never carries a contact. Reads only.read
get_prospect_listOne prospect list's companies, each with what its public website says (facts with source) and the rule-based ICP match per trait (matched / missed / unknown). Never carries a contact. Reads only.read
research_prospect_listRead each pending company's public website (homepage + one page) and record what it says and the rule-based ICP match per trait. Companies only — never a person. Resumable.write
get_billing_accountThe tenant's billing standing, card on file (brand and last four only), mandate and per-currency balances.read
get_rate_cardThe billing profile's resolved rate card per currency: its source, in force, upcoming versions and history.read
get_billing_accrualsPer currency for one month: posted (certified) fees, the provisional estimate, uncovered days and held spend.read
list_billing_statementsOne billing profile's statements, newest first, optionally by status.read
get_billing_statementOne statement: lines by service month, restatement references, gaps, tax status and payment attempts.read
preview_billing_paymentThe amount, the payment method and a confirmation token for paying one statement now.read
list_ad_account_assignmentsThe tenant's ad accounts with the advertiser and billing profile each bills to, and any pending change.read
list_change_requestsPending and decided change requests: an account admin sees the account's, everyone else their own.read
get_change_requestOne change request: the change asked for, the preview shown, its decision and result.read

Agent roles

15 pre-built roles ship as MCP prompts over the same tool surface — instructions, not runtime code, so none of them can bypass a guardrail. Each owns one mutation family or none; every other role hands off.

onboardingThe first-run guide: builds the business's Brain from its website with you, section by section with each item's source, and stores only what you approve. research_brand · apply_brand_research
account-auditorRead-only account inspection that returns a findings report with the wasted-spend arithmetic shown. read-only
media-strategistTurns goals, budget, and account history into a media plan with dry-run-validated campaign drafts. read-only
media-mix-modelerLong-window cross-platform budget-mix analysis and reallocation scenarios, with its method limits stated. read-only
campaign-traffickerBuilds the approved plan into campaigns and flights — created paused, behind a pre-activation checklist. creates · builds · activates
account-optimizerRuns the weekly supervised optimization loop over the recommendation ladder. apply_recommendation
performance-analystWrites the weekly performance narrative, correlated to the change log, with data-quality footnotes. read-only
creative-strategistDetects fatigue, drafts briefs and copy variants, and runs governed creative swaps on Meta. replace_ad_creative
audience-architectSegmentation strategy and audience creation and sync under the SHA-256 hashing discipline. audience tools
flight-pacerKeeps cross-platform flights on budget and schedule with run-rate pacing proposals. flight budgets
guardrail-wardenGuardrail posture, anomaly triage, containment pauses, and rollbacks. set_policy · pause · rollback
conversational-plannerTurns one supplier brief into per-platform dry-run previews on the AI-surface connectors — declared category and parties, policy findings, a handoff for approval. Never goes live. read-only
brand-stewardKeeps the tenant's shared memory: the brand context, the creative check, and the learnings ledger — control-plane writes only. set_brand_context · record_learning
market-analystKeeps the competitor list and a dated ledger of what competitors' public sites say, and proposes discoveries for the operator to keep or dismiss — control-plane writes only. add_competitor · research_competitors · resolve_discovery
prospect-researcherResearches the companies on the account's own uploaded prospect lists from their public websites, with a rule-based match per trait and no score. Never a person. research_prospect_list

Normalized schemas

Metrics come back in one shape regardless of platform. Money is always integer micros with a declared currency; ROAS and CPA are derived (revenue ÷ spend, spend ÷ conversions). A day inside its platform’s restatement window is provisional, a day never synced is uncovered — never zero.

// every get_report row, every platform
{
  "platform": "meta", "campaignId": "c_8841",
  "date": "2026-07-31", "currency": "USD",
  "spendMicros": 412100000, "conversions": 22,
  "revenueMicros": 1401140000
}

Full schema and metric caveats live in the repo: docs/api-contract.md and docs/openapi.json.