AdsAgent / MCP Guide

AdsAgent MCP Agent Onboarding

This public page mirrors the canonical onboarding markdown used by AdsAgent MCP. When mcp.guide_version changes, reconnect the MCP server, reread the guide resources, and use this page as the human-readable companion.

Protocol

adsagent-mcp-v1

Guide Version

2026-07-05.1

Production MCP

https://adsagent.md/mcp

Test MCP

https://t.adsagent.md/mcp

This guide is for two audiences:

  • Platform users who need to connect an external agent to AdsAgent MCP
  • Agent developers who need the exact onboarding and setup workflow

Public web guide:

  • Production: https://adsagent.md/docs/mcp-onboarding
  • Test: https://t.adsagent.md/docs/mcp-onboarding

Quick start — connect AdsAgent MCP with OAuth

If all you want to do is wire AdsAgent into an AI client you already use, this is the complete flow. It takes about three minutes, and you do not need to copy or paste any tokens.

Step 1 — Add the hosted MCP server URL.

Use your client's remote MCP / custom connector flow and register AdsAgent as a hosted HTTP MCP server:

  • Meta: https://adsagent.md/mcp
  • TikTok: https://tiktok.adsagent.md/mcp
  • Google Ads: https://google.adsagent.md/mcp — OAuth discovery on this host is still rolling out; until it completes, connect Google Ads with the bearer-token setup in section 9 (the same token works on all three servers)

Do not install a local AdsAgent process or bridge. AdsAgent runs the MCP server on its own infrastructure; your client only needs the HTTPS endpoint.

Step 2 — Select OAuth authentication.

When the MCP client asks how to authenticate, choose OAuth. The client opens an AdsAgent authorization page in your browser.

Step 3 — Approve AdsAgent in the browser.

Log in (if you are not already) and approve the connection. The browser hands control back to your MCP client automatically. There is no token to copy.

Step 4 — Run the first safe test prompt.

Return to your agent and run:

> *"List my AdsAgent products and ask me which product to inspect."*

The agent calls products_list and setup_get_status against AdsAgent under OAuth Safe Action Mode (see below). If the calls succeed, AdsAgent returns your products and onboarding state. If they fail, the most common causes are:

  • OAuth was cancelled or not completed — reconnect the connector and approve in the browser
  • Wrong URL (Meta production is https://adsagent.md/mcp, TikTok is https://tiktok.adsagent.md/mcp)
  • Client did not reconnect after the connector change

Once the first read succeeds, continue with the setup path for the server you connected. Meta and TikTok account-connect flows use their own platform authorization screens. Google Ads is read-only in the current public beta, so the agent should stay on account discovery and reporting unless a later Google guide explicitly enables mutation.

OAuth Safe Action Mode

OAuth connections are not read-only — since oauth_safe_v2 (2026-06-11) their permission surface is close to a full operator token, with two deliberate differences.

They can (one-shot, same as an operator):

  • read product cards, templates, ad accounts, insights, and exports
  • create or update user-owned templates, interest packs, and creatives
  • run asset refreshes, MMP connection lifecycle, and config writes

Live-delivery changes are confirmation-gated on platforms where mutation is enabled. On Meta, anything that changes a RUNNING campaign / ad set / ad — status, budget, bid — or publishes a new launch goes through a two-step approval. The prepare step echoes the live current value straight from the Meta backend (never the agent's claim), re-activating a paused entity or a >=5x budget increase carries a loud warning, and the confirm token is single-use with a 15-minute expiry. The agent must read the summary back to you and only confirm after you explicitly approve. Google Ads is read-only in the current beta; do not present Google Ads as having Meta-style publish, budget, bid, or QuickCreate flows.

They cannot (the single hard block):

  • pull raw row-level data (insights_query_rows) or internal diagnostics

In practice an OAuth agent's safe first-run loop is:

  1. setup_get_status — read onboarding readiness
  2. products_list — list the user's products
  3. ask the user which product and date range to inspect
  4. for one requested scope, call insights_query_overview; for multiple requested scopes, call insights_query_batch_overview
  5. report the SERVER-computed totals (the first line of every aggregate response) verbatim — never sum rows yourself because rows may be truncated and accounts often carry multiple campaigns with identical names
  6. create templates or prepare ad/copy/budget changes only on platforms where those mutations are enabled, and only when the user asks
  7. confirm any change to live delivery only after explicit user approval

The rest of this guide is the full reference for agent developers and operators who want to understand the tool surface in detail.

---

1. Connect the agent over OAuth

Configure the external agent or MCP client with one MCP URL per product and OAuth authentication:

  • Meta: https://adsagent.md/mcp
  • TikTok: https://tiktok.adsagent.md/mcp
  • Test (Meta): https://t.adsagent.md/mcp

Select OAuth when the client asks how to authenticate, approve in the browser, and the client stores the connection. No bearer token needs to be generated or pasted for normal use. (Operators and legacy clients that cannot complete OAuth can fall back to bearer tokens — see the advanced section at the end.)

Recommended first call:

  • setup_get_status

This returns:

  • mcp.protocol_version and mcp.guide_version for tool-guide cache detection
  • state for machine-readable readiness data
  • narrative for user-facing onboarding guidance

If mcp.guide_version is different from the agent's cached value, do a transport-level reconnect (close + reopen the hosted HTTP MCP connection — NOT just sending another initialize request, which most clients keep tools/list cached across). After transport reconnect: re-list tools, read adsagent://guide/brief FIRST (a ~40-line rules digest — reading it is enough to operate safely), then rerun setup_get_status. Do NOT read adsagent://guide/tools end-to-end — it is a large reference document; consult it on demand when a specific tool's parameters are unclear. See agent_gotchas "Tool descriptions cache separately from resources" for why this matters.

For insights and overview reads:

  • agents should pass an explicit attribution window when the user asks for attribution-sensitive analysis
  • if the agent omits it, AdsAgent defaults to value
  • insights_query_overview returns effective_attribution so the agent can confirm the final read contract back to the user
  • for one requested scope, use ordinary overview: call insights_query_overview once for that product/account/date/filter scope
  • for multiple requested scopes, use batch overview: call insights_query_batch_overview once with the requested scopes
  • Do not fan out with concurrent client-side insights_query_overview calls for multiple scopes; the server-side batch overview owns concurrency, aggregation, and overload control
  • if insights_query_overview or insights_query_batch_overview returns HTTP 429, honor the Retry-After header exactly, back off for that window, and retry only after the server-specified delay
  • treat HTTP 429 as MCP backpressure / rate limiting. Do not describe MCP backpressure as a 503 or origin failure; 503/origin failure is an infrastructure incident lane, not the normal agent-facing explanation for overload control
  • insights_query_overview supports both view_mode=grouped and view_mode=daily
  • grouped mode is the management-friendly rollup; daily mode is analysis-only and returns one row per date + entity
  • if the user wants editable status / budget / bid fields, agents should use view_mode=grouped with management=true
  • if the user wants day-by-day table reads, agents should use view_mode=daily and may sort by date
  • insights_export_csv supports both grouped and daily exports
  • grouped export keeps the selected range explicit with range_start and range_end
  • daily export emits one row per date with a date column so cross-day files are unambiguous
  • insights_query_overview is cache-only for AppsFlyer cohort data
  • the response meta.data_freshness.label tells the agent whether the selected range is fresh, syncing, stale, or missing (block also carries interval_min + max_staleness_min + last_pulled_at for context)
  • if the range is missing and the user needs an immediate backfill, the agent should use mmp_fetch_cohorts explicitly instead of assuming the read path will fetch it

For reusable audience packs:

  • interests_fetch_from_adset previews one live ad set's flexible_spec into an unsaved candidate
  • interests_save_fetched_pack persists that fetched candidate as a saved global pack
  • interests_list/get/archive manage saved packs after creation
  • saved packs replace ad set flexible_spec when applied in QuickCreate / Template / Copy Ad / MCP quick_create
  • agents should prefer exact interest_pack_name in user-facing flows and keep AdsAgent internal pack ids hidden

For MMP and product reads:

  • mmp_get_state lists all MMP connections for the current AdsAgent user
  • one provider can have multiple independent connections; treat connection_id as the stable operational handle
  • AppsFlyer metadata, cohort configs, and cohort rows are isolated per connection_id + provider_app_id
  • products_list returns saved products plus mmp_match_count, mmp_matches, mmp_events, mmp_event_metrics, and saved funnel_events
  • Meta funnel events keep plain names such as purchase
  • MMP funnel events use provider-prefixed keys such as af:ug_new_user_payment:count
  • products_get_funnel_values is the per-ad funnel visualization source — returns funnel_data keyed by Meta ad_id, joining Meta insights (ad_id) with MMP cohort rows (af_ad_id). It is NOT an aggregate reader; when the product has no recently active Meta ads, the JOIN is legitimately empty and funnel_data: {} is the correct return, not a bug. For "how many paid today for product X", always use mmp_insights_get_product_event_today instead.
  • products_get_funnel_values may also return data_freshness.label to describe whether the requested AppsFlyer range is fresh, syncing, stale, or missing in cache (block also carries interval_min + max_staleness_min + last_pulled_at for context)
  • when the user asks a question such as "today this MMP event's paid count, paid users, and revenue", call products_list first to resolve the saved product and inspect its mmp_events / mmp_event_metrics
  • after the saved product is known, call mmp_insights_get_product_event_today(product_id, event_name, today?) for today's event summary or mmp_insights_query_product_event_summary(product_id, event_name, date_from, date_to) for a date range
  • mmp_insights.* returns only product-scoped aggregated MMP totals for one event_name; it does not mix Meta actions, saved funnel sequencing, or per-ad detail into the response
  • mmp_insights_get_product_event_today and mmp_insights_query_product_event_summary also accept optional breakdown=country | placement | campaign | adset | ad. When breakdown is adset or ad, each breakdown_rows entry also carries an id field
  • when breakdown is set, AdsAgent keeps the same top-level count / unique / revenue totals and adds cleaned breakdown_rows
  • REQUIRED channel_pid parameter on mmp_insights.*: pass one slug like facebook_int / googleadwords_int / tiktokglobal_int for that single channel, OR pass 'all' to fan out across every channel the user has configured. Omitting channel_pid returns a ValidationError (intentional — the prior silent-default-to-Facebook behavior was retired in Round 2 because it caused 23% of revenue to be invisible to agents in cross-channel cohort questions).
  • when channel_pid: 'all', the response shape adds by_channel: [...] (alphabetically sorted per-channel rows) + total: {...} (aggregate). For a single slug, the response stays in single-channel shape (no by_channel / total block).
  • invalid slug returns a "Did you mean X?" hint based on the user's actually-configured channels (not all 50+ AppsFlyer slugs).
  • mmp_insights.* is a live read against AppsFlyer — data is always the latest, no local cache is consulted during the read
  • use mmp_fetch_cohorts explicitly only when the user asks for a manual backfill of the local cache (separate from the live read path above)
  • mmp_fetch_cohorts accepts optional grouping_scope; omit it for total cache, use country for country breakdown cache, and placement for placement breakdown cache
  • AppsFlyer cohort pulls cannot combine country and placement in one request, so those scopes must be fetched and refreshed separately
  • only use products_get_funnel_values when the user explicitly wants the per-ad funnel visualization (dashboard view), NOT for aggregate totals. Empty funnel_data: {} from this tool just means no matching ad_id ↔ af_ad_id pairs in the window — route those requests to mmp_insights.* instead of trying to debug the format.

2. Facebook connect flow

If setup reports that no Facebook user is connected, the agent should run:

  • setup_begin_facebook_connect

That tool returns a direct Facebook authorize_url and a compatibility connect_url. Agents should prefer authorize_url. The user opens that link in a browser, authorizes Facebook, and AdsAgent finishes the callback flow automatically.

Important link behavior:

  • the connect link is valid for 10 minutes
  • the authorize_url can be opened on another device or sent to a teammate
  • completion still binds the resulting Facebook user to the same AdsAgent user who created the ticket
  • if the user also has the GUI open, AdsAgent can refresh the original page automatically, but the agent should still trust setup_check_facebook_connect as the source of truth

After authorization:

  1. The user returns to the agent and says they are done
  2. The agent calls setup_check_facebook_connect
  3. AdsAgent continues the normal post-bind flow:
  • routing refresh
  • asset refresh
  • first historical sync submission

The agent should not scrape the GUI to guess whether connect succeeded. It should always read the backend status through MCP.

The canonical safe first-run sequence for a normal OAuth user is:

  1. setup_get_status
  2. products_list
  3. ask the user which product / date range to inspect
  4. insights_query_overview for one requested scope, or insights_query_batch_overview for multiple requested scopes
  5. on Meta only, setup_begin_facebook_connect when Facebook is not connected
  6. on Meta only, setup_check_facebook_connect after the user finishes authorization
  7. template / draft / create flows only on platforms where mutation is enabled, and only when the user asks for them

Heavy setup tools — setup_analyze_products, setup_ensure_baseline_templates, assets_refresh_all, and manual pull/breakdown tasks — are operator/maintenance actions. They are documented in §9 (advanced/operator) and should not be presented as normal OAuth first-run steps.

4. What each setup tool does

setup_get_status

Use this first. It answers:

  • whether assets are ready
  • whether historical sync is ready
  • whether products are ready
  • whether templates are ready
  • whether QuickCreate, Partnership Ads, and MetaKol are ready
  • what the next recommended action is

setup_begin_facebook_connect

Use this when no Facebook user is connected yet.

It returns:

  • connect_id
  • direct authorize_url
  • compatibility connect_url
  • link_behavior with ticket TTL and cross-device sharing rules
  • user-facing instructions

setup_check_facebook_connect

Use this after the user completes Facebook authorization.

It reports:

  • whether the connect completed
  • which Facebook user name was created
  • whether the binding task is still running
  • whether asset refresh and first sync have reached a usable state
  • ready so the agent does not have to infer usability from raw status fields
  • sync_stage as one of waiting_auth, binding, asset_refresh, first_pull, ready, failed, or expired
  • next_action and recommended_tool for the next step
  • poll_after_ms when the correct action is to keep polling
  • user_message as the default end-user explanation

setup_readiness_check

Use this before ad-creation workflows. It answers whether the account is ready for:

  • QuickCreate
  • Partnership Ads
  • MetaKol

> setup_analyze_products and setup_ensure_baseline_templates are operator/maintenance tools, not normal OAuth first-run steps. See §9.

5. MMP Setup Through MCP

Use the mmp.* tools when the user wants to connect AppsFlyer or inspect cohort data.

Recommended sequence:

  1. mmp_get_state
  2. mmp_connect when the desired connection does not exist yet — this also auto-matches the user's saved products against the AppsFlyer metadata by store_url / bundle id
  3. mmp_refresh_connection when synced metadata is stale
  4. products_list to confirm auto-match succeeded (each product's mmp_match_count should be ≥ 1 if it was supposed to match)
  5. products_link_mmp_app ONLY when a product stays at mmp_match_count=0 after step 2 — manually attach it to the right (connection_id, provider_app_id) from mmp_get_state
  6. mmp_save_cohort_config for one connection_id + provider_app_id
  7. mmp_fetch_cohorts for the desired date range

Important MMP rules:

  • connection_id is public and should be surfaced when the user needs to distinguish multiple tokens under the same provider
  • AppsFlyer cohort config is per connection_id + provider_app_id, not global per provider
  • the current default AppsFlyer cohort contract uses user_acquisition
  • MMP product matching currently relies on the synced metadata and product rules; agents should inspect products_list after a sync to confirm each saved product is linked, and fall back to products_link_mmp_app only when auto-match missed
  • adjust is still coming soon; agents should expect coming_soon instead of live metadata/cohorts there

6. Agent behavior guidelines

Agents should follow these rules:

  • Operate in OAuth Safe Action Mode by default: read summarized data, ask for product/date scope before insights, create templates and prepare drafts only when asked, and confirm publish/copy only after explicit user approval
  • Always start with setup_get_status
  • Store the latest seen mcp.guide_version per server (Meta and TikTok track independently). When it changes, do a transport-level reconnect (close + reopen the connection — soft initialize re-send does NOT refresh the cached tools/list); then re-list tools and read that server's brief/onboarding guide resource (Meta: adsagent://guide/brief; consult the full adsagent://guide/tools on demand, never end-to-end).
  • Prefer narrative when speaking to the user
  • Prefer state when deciding the next tool call
  • Prefer setup_check_facebook_connect.ready, next_action, and user_message over hand-written interpretations of raw binding task fields
  • For insights_query_overview, prefer explicit attribution input over silent defaults; if the user did not choose one, tell them you are using the default value window and read back effective_attribution from the response. Also choose view_mode=grouped vs view_mode=daily deliberately instead of relying on defaults when the user cares about management vs day-by-day analysis.
  • For insights_export_csv, ask whether the user wants grouped totals or day-by-day rows when that choice matters; if they do not care, grouped is the safe default. Broad all-account exports should be explicitly requested and confirmed, not run by default.
  • For mmp_get_state, prefer inspecting the existing connections before creating another token-backed connection; one provider can have multiple connections and they are not interchangeable
  • For mmp_save_cohort_config and mmp_fetch_cohorts, always keep the chosen connection_id and provider_app_id together in the same user-facing explanation, and read back grouping_scope when a manual backfill targets country or placement
  • For pure MMP event-summary reads, resolve the saved product with products_list first, then use mmp_insights_get_product_event_today for today's view or mmp_insights_query_product_event_summary for a date range. These hit AppsFlyer live so the numbers are always the latest. Only use products_get_funnel_values when the user explicitly wants the saved mixed Meta/MMP funnel contract
  • Add breakdown (country | placement | campaign | adset | ad) only when the user explicitly asks for that dimension. channel_pid is REQUIRED on every mmp_insights.* call — pass one slug like facebook_int / googleadwords_int / tiktokglobal_int for one channel, OR 'all' for fan-out + by_channel breakdown + aggregate total. Omitting it returns ValidationError (the silent-default-to-Facebook behavior was retired in Round 2).
  • For products_list, treat mmp_match_count, mmp_matches, mmp_events, and mmp_event_metrics as the source of truth for MMP-linked products and event availability
  • When explaining funnel contracts, keep Meta event names plain and read provider-prefixed MMP keys back exactly, for example af:ug_new_user_payment:count
  • When products_get_funnel_values or insights_query_overview returns cohort_sync, tell the user whether the AppsFlyer cohort range is fresh, syncing, stale, or missing, and include the date range in the explanation
  • Prefer public name-based contracts over internal ids:
  • use exact template_name with templates_get/update/delete when possible
  • use exact creative selection_key with creatives_list/get/delete
  • do not show AdsAgent internal template / creative / folder ids to end users
  • task_id and connection_id are the only internal-style ids that should routinely be surfaced to the user
  • Never assume Facebook connect succeeded until setup_check_facebook_connect confirms it
  • Never ask the user to manually inspect database or task internals
  • Do not run operator/maintenance tools (asset refresh, baseline-template rebuild, raw pulls, internal diagnostics) under a normal OAuth session
  • Treat readiness_check as the final gate before ad-creation workflows

Platform copy guardrail:

  • Meta-only: Facebook connect, QuickCreate, creative drawer, and Meta table-schema language are Meta-specific. Do not apply those words or assumptions to Google Ads or TikTok.
  • Google Ads is read-only in the current beta. Google Ads account discovery and reporting may be described; Google Ads campaign creation, edit, pause, budget, bid, and QuickCreate flows must not be advertised unless the Google MCP guide changes first.
  • TikTok has its own server and guide resources. Keep TikTok setup, permissions, and reporting language tied to https://tiktok.adsagent.md/mcp and the TikTok guide; do not borrow Meta Facebook-permission or QuickCreate wording.

7. Cross-product semantics every agent should know

Identity check (cross-product)

setup_get_status on either server returns a connection block:

jsonc
{
  "connection": {
    "email": "[email protected]",     // ← globally unique per supabase auth.users record
    "auth_id": "<auth.users.id UUID>",  // ← also globally unique cross-product
    "platform_user_id": "<UUID>"    // ← per-product scoped: Meta's public.platform_users.id ≠ TikTok's tiktok.platform_users.id for the same human
  }
}

For cross-product identity verification (the agent confirming "is this the user I think it is on both servers?"), use email or auth_id — both globally unique. Do NOT use platform_user_id for cross-product comparison; it's a per-product table primary key, different by design across products. (See agent_gotchas "platform_user_id is per-product scoped" for the full incident this gotcha came from.)

Auto-pull intervals (insights + assets)

setup_get_status returns auto_pull = {insights: {...}, assets: {...}} describing the user's auto-pull schedule:

jsonc
{
  "auto_pull": {
    "insights": {
      "interval_min": 60,            // user-set: 30 / 60 / 120 / 180
      "enabled": true,
      "last_pulled_at": "2026-05-06T...",
      "next_pull_estimate": "2026-05-06T..."
    },
    "assets": {
      "interval_min": 120,           // user-set: 60 / 120 / 180
      "enabled": true,
      "last_pulled_at": "...",
      "next_pull_estimate": "..."
    }
  }
}

Settings are user-controlled in the dashboard; the agent surfaces them but should not silently change them.

To change interval programmatically (advanced — usually the user does it in GUI):

  • PATCH /api/settings/auto-pull-interval with body {interval_min: 30 | 60 | 120 | 180} (Meta) or {interval_min: 30 | 60 | 120 | 180} (TikTok). Settings sync between products via loopback — changing on Meta auto-propagates to TikTok and vice versa.

mmp_insights.* channel_pid REQUIRED + 'all' value

(Already covered in §1 above — see the mmp_insights.* bullets.) Recap:

  • channel_pid is REQUIRED on every mmp_insights.* call
  • pass one slug for one-channel, OR pass 'all' for fan-out + by_channel breakdown + aggregate total
  • omit → ValidationError (NOT silent FB-only default like the pre-Round-2 behavior)

agent_gotchas — read it once, save scrolls later

Both servers' guide/tools resource carries an agent_gotchas list:

  • Meta: ~11 entries as of 2026-05-04.9
  • TikTok: ~13 entries as of 2026-05-04.4

Read it on first connect + after every mcp.guide_version bump. Each entry follows {issue, wrong, right, why} shape. Topics covered (non-exhaustive):

  • channel_pid REQUIRED on mmp_insights.*
  • platform_user_id per-product scoped (use auth_id/email cross-product)
  • mapped_events.send_revenue describes postback forwarding ≠ cohort completeness
  • selected_channel_pids is schedule-only, NOT a read-time filter
  • updated_at is "row last touched", NOT "content changed"
  • Tool descriptions cache separately from resources — transport-level reconnect required
  • (TikTok-only) MMP query default behavior + identity verification

If your agent hits a confusing response shape and the docstring doesn't explain it, check agent_gotchas first — there's a 70%+ chance the friction is already documented.

8. Meta-only: Start QuickCreate Through MCP

This section applies to the Meta MCP server. Do not copy it into Google Ads or TikTok instructions unless those platform guides explicitly expose the same public contract.

After setup_readiness_check says QuickCreate is ready, prepare a publish draft. QuickCreate prepares only — nothing is published until the user explicitly approves.

  1. Call templates_list and choose an owned template
  2. Call creatives_list and choose one or more creative selection_key values, or collect partnership rows from the user
  • if the user wants reusable audience targeting, call interests_list/get and choose one saved pack by exact name
  • templates_list / templates_get return public template contracts without internal template ids
  • creatives_list / creatives_get return public creative contracts keyed by selection_key, not internal creative ids
  • interests_list / interests_get return public interest-pack contracts keyed by exact name, not internal AdsAgent ids
  • for partnership rows, prefer partnership_ads.quick_create instead of manually building creative_source.mode=partnership
  1. Ask whether to publish immediately or tomorrow. For immediate publish, pass execution.start_mode=immediate or omit it. For tomorrow, pass execution.start_mode=tomorrow; AdsAgent computes account-timezone tomorrow 00:05 like the GUI.
  2. Call campaigns_quick_create
  3. If the result is status=needs_input, that means ask the user for the missing fields and retry only with user-provided fields — collect the returned missing_requirements and call campaigns_quick_create again with execution / overrides filled. Do not guess hidden payload fields.
  4. If the result is status=operator_review_required, stop and ask the operator — do not attempt to work around it under a normal OAuth session.
  5. Show the returned approval_request.summary and approval_request.warnings
  6. Explain structure using the GUI nesting rule:
  • requested structure is per campaign, not flat totals
  • 5 cams, 5 sets, 1 ads means create 5 campaigns; each campaign gets 5 ad sets; each ad set gets 1 ad per selected creative or partnership row
  • total ad sets = campaign_count × adgroup_count
  • total ads = campaign_count × adgroup_count × ads_per_adset
  • example with one selected creative: 5 cams, 5 sets, 1 ads means 5 campaigns, 25 ad sets, 25 ads
  1. Remind the user of the planned campaign names, ad set names, statuses, and publish start label before approval
  2. campaigns_quick_create_confirm(confirm_token) requires explicit user approval — only call it after the user has approved
  3. If the user denies, call campaigns_quick_create_deny(confirm_token)
  4. After confirm returns task_id, poll tasks_get_status(task_id) until terminal=true
  5. Tell the user the final status, routed FB user tag, created campaign/adset/ad ids, errors, template, architecture, and publish start time

Important: campaigns_quick_create does not publish. It only prepares a short-lived draft and approval request. When the user approves, AdsAgent submits the same create task path as the GUI QuickCreate flow: campaign, ad set, and ads default to ACTIVE; budget / bid / spend-cap are inherited from the selected template unless the agent passes an explicit execution override. If the user asks to launch paused, pass execution.campaign_status, execution.adset_status, and/or execution.ad_status as PAUSED. Approval summaries must show all three statuses before confirm. For creative-mode publish, the selected asset must be publish-ready and include a resolvable asset URL, just like the GUI creative picker.

QuickCreate Prepare Contract

Required MCP inputs:

  • ad_account_id
  • template_id or template_name
  • creative_source
  • execution.budget_amount, unless the selected template already contains campaign or ad set budget. Do not ask for a new budget when a loaded template already has one.

Optional MCP inputs:

  • interest_pack_name when the user wants one saved global audience pack injected into ad set flexible_spec

Preferred public references:

  • prefer exact template_name over internal template ids in agent-to-user conversations
  • prefer exact interest_pack_name over internal interest-pack ids in agent-to-user conversations
  • prefer creative selection_key / filename over internal creative ids
  • do not expose AdsAgent internal template / creative / folder ids in user-facing text
  • task_id is still allowed because users need it for async follow-up

Optional start input:

  • execution.start_mode: immediate or tomorrow; default immediate

Optional execution status inputs:

  • execution.campaign_status: ACTIVE or PAUSED; default ACTIVE
  • execution.adset_status: ACTIVE or PAUSED; default ACTIVE
  • execution.ad_status: ACTIVE or PAUSED; default ACTIVE

creative_source examples:

json
{"mode": "creative", "creative_names": ["selection_key_from_creatives_list.png"]}
json
{"mode": "partnership", "partnership_rows": [{"ad_code": "adcode-xxx", "ad_name": "ad-001"}]}

Partnership Ads Shortcut

When the user is launching Partnership Ads from ad_code + ad_name rows, agents should prefer:

  1. partnership_ads.quick_create
  2. partnership_ads.quick_create_confirm
  3. partnership_ads.quick_create_deny

This shortcut uses the same draft, approval, and publish path as campaigns_quick_create, but exposes a clearer public contract for partnership mode. quick_create prepares; quick_create_confirm requires explicit user approval.

Template-backed values that may need overrides:

  • page_id via overrides.page_id
  • tracking_id via overrides.tracking_id for H5 / web templates
  • store_url via overrides.store_url
  • link_url via overrides.link_url or overrides.store_url
  • application_id in the selected template's promoted object

Title/headline and body text may be empty. When they are empty, AdsAgent can return warnings; treat them as reminders, not prepare blockers. Campaign and ad set naming are not blockers either, but the agent should still surface the planned names from the approval summary before confirm.

QuickCreate Task Follow-up

campaigns_quick_create_confirm returns quickly. It does not wait for Meta publishing to finish.

After confirm:

  1. Read task_id from confirm result
  2. Call tasks_get_status with that task_id
  3. If terminal=false, wait briefly and call tasks_get_status again
  4. If terminal=true, report the final execution_summary, result, logs_tail

tasks_get_status.execution_summary includes:

  • action, usually quick
  • ad_account_id
  • template_name
  • publish_start_time
  • publish_start_label, usually Immediate or the scheduled time
  • resolved_fb_user_tag
  • planned nested structure and totals, such as 5 cams, 5 sets, 1 ads => 5 campaigns, 25 ad sets, 25 ads
  • planned campaign / adset / ad names
  • created campaign / adset / ad counts
  • error_count

For user-facing completion, report the action used, routed FB user tag, template, publish start time, planned architecture, total campaign/ad set/ad counts, created Meta ids, and errors.

Telegram Approval

For Telegram, render the approval request as a message with inline buttons:

  • See more
  • Allow
  • Deny

Do not put the full confirm_token in Telegram callback_data.

Keep confirm_token in the agent's short-lived conversation state. Put only a small local approval id in Telegram callback_data. When the user taps Allow, resolve the local approval id to confirm_token, then call campaigns_quick_create_confirm.

9. Advanced: operator or legacy bearer-token setup

Most users should use OAuth (see the quick start at the top). This section is for internal operators, automation, legacy clients, or smoke tests that cannot complete an OAuth flow, plus the operator/maintenance tools that are not part of normal OAuth Safe Action Mode.

Bearer-token connection (operator / legacy only)

Open Settings > MCP Access in AdsAgent. You will see:

  • Production MCP URL
  • Test MCP URL
  • one active MCP token slot

Rules:

  • Only one active MCP token is allowed per platform user
  • The generated token is shown once — copy it immediately and store it somewhere safe (1Password, system keychain)
  • To replace a token, revoke the current token first, then generate a new one (mcp_live_* tokens; revoke/regenerate semantics apply on both products via the twin-stars loopback)

The token is used as:

http
Authorization: Bearer <mcp_token>

Use:

  • Meta production: https://adsagent.md/mcp
  • TikTok production: https://tiktok.adsagent.md/mcp
  • Google Ads production: https://google.adsagent.md/mcp
  • Test (Meta): https://t.adsagent.md/mcp

One token works on all three MCP servers. Meta/TikTok mirroring is automatic via the twin-stars wire (server-side loopback after creation; revoking on either side propagates to both); Google resolves the same shared token through your central AdsAgent identity. You don't generate separate per-server tokens — use the same Authorization: Bearer <your-token> header on every connector.

Google requires a central-identity token: generate the token while signed in via the AdsAgent central login (Continue with Google). A token minted before central login carries no cross-channel identity — Meta keeps accepting it, but Google rejects it with legacy_token_no_identity and asks you to reissue (revoke + generate again while signed in centrally).

Operator / maintenance tools

These are not normal OAuth first-run steps. Run them only as an operator or when explicitly performing maintenance:

setup_analyze_products

Creates or refreshes baseline product group analysis for the current platform user.

setup_ensure_baseline_templates

Ensures that baseline templates exist for analyzed products.

Other operator/maintenance actions

  • assets_refresh_all — asset refreshes / baseline rebuilds
  • manual pull / breakdown backfill tasks
  • raw-row reads and internal diagnostics
  • broad all-account exports (must be explicitly requested and confirmed)

Example operator setup script

This is the shortest working operator-led onboarding path (full setup, beyond Safe Action Mode):

  1. Operator connects (OAuth, or bearer token for legacy clients) and calls setup_get_status
  2. If blocked on Facebook, operator calls setup_begin_facebook_connect
  3. User opens the returned connect URL and authorizes Facebook
  4. User tells the agent: done
  5. Operator calls setup_check_facebook_connect
  6. Operator calls setup_analyze_products
  7. Operator calls setup_ensure_baseline_templates
  8. Operator calls setup_readiness_check
  9. Operator guides the user into QuickCreate, Partnership Ads, or MetaKol

10. TikTok MCP server (twin-stars)

AdsAgent runs a sister MCP server for TikTok at:

  • Production: https://tiktok.adsagent.md/mcp
  • Test: https://tiktok.t.adsagent.md/mcp (if applicable)

Connect it the same way as Meta: add the hosted HTTP MCP server URL and choose OAuth. Approve in the browser; no separate TikTok credential is needed.

Configure both hosted HTTP servers in your MCP client side-by-side:

  • adsagent -> https://adsagent.md/mcp
  • adsagent-tiktok -> https://tiktok.adsagent.md/mcp

Authorize each over OAuth. (Operators and legacy clients using bearer tokens: one token works on both servers via the twin-stars loopback — see §9.)

Each server has its own tool surface — the agent calls setup_get_status on each independently. Cross-product version drift is expected and acceptable (Meta and TikTok bump MCP_GUIDE_VERSION independently).

TikTok-specific resources:

  • tiktok-adsagent://guide/onboarding — TikTok-side quickstart
  • tiktok-adsagent://guide/onboarding — TikTok-side agent_gotchas list + tool catalog

TikTok exposes the same auto_pull intervals + identity check semantics as Meta — see §7.

11. Google Ads MCP server

AdsAgent runs a sister MCP server for Google Ads at:

  • Production: https://google.adsagent.md/mcp

Today this server accepts bearer-token connections only (section 9) — the same single mcp_live_* token used for Meta and TikTok, provided it was generated under the central AdsAgent login (see the legacy_token_no_identity note in section 9). OAuth discovery on this host is rolling out; once it completes, the OAuth quick start at the top covers Google Ads too and one browser approval will cover all three servers.

Configure it side-by-side with the other two:

  • adsagent -> https://adsagent.md/mcp
  • adsagent-google -> https://google.adsagent.md/mcp
  • adsagent-tiktok -> https://tiktok.adsagent.md/mcp

Google-specific resources:

  • googleadsagent://guide/onboarding — Google-side quickstart (+ googleadsagent://guide/safe-mode)

Each server has its own tool surface and bumps its guide version independently, same as TikTok (§10).