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 ishttps://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:
setup_get_status— read onboarding readinessproducts_list— list the user's products- ask the user which product and date range to inspect
- for one requested scope, call
insights_query_overview; for multiple requested scopes, callinsights_query_batch_overview - 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
- create templates or prepare ad/copy/budget changes only on platforms where those mutations are enabled, and only when the user asks
- 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_versionandmcp.guide_versionfor tool-guide cache detectionstatefor machine-readable readiness datanarrativefor 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_overviewreturnseffective_attributionso the agent can confirm the final read contract back to the user- for one requested scope, use ordinary overview: call
insights_query_overviewonce for that product/account/date/filter scope - for multiple requested scopes, use batch overview: call
insights_query_batch_overviewonce with the requested scopes - Do not fan out with concurrent client-side
insights_query_overviewcalls for multiple scopes; the server-side batch overview owns concurrency, aggregation, and overload control - if
insights_query_overvieworinsights_query_batch_overviewreturns HTTP 429, honor theRetry-Afterheader 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_overviewsupports bothview_mode=groupedandview_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=groupedwithmanagement=true - if the user wants day-by-day table reads, agents should use
view_mode=dailyand may sort bydate insights_export_csvsupports bothgroupedanddailyexports- grouped export keeps the selected range explicit with
range_startandrange_end - daily export emits one row per date with a
datecolumn so cross-day files are unambiguous insights_query_overviewis cache-only for AppsFlyer cohort data- the response
meta.data_freshness.labeltells the agent whether the selected range isfresh,syncing,stale, ormissing(block also carriesinterval_min+max_staleness_min+last_pulled_atfor context) - if the range is missing and the user needs an immediate backfill, the agent should use
mmp_fetch_cohortsexplicitly instead of assuming the read path will fetch it
For reusable audience packs:
interests_fetch_from_adsetpreviews one live ad set'sflexible_specinto an unsaved candidateinterests_save_fetched_packpersists that fetched candidate as a saved global packinterests_list/get/archivemanage saved packs after creation- saved packs replace ad set
flexible_specwhen applied in QuickCreate / Template / Copy Ad / MCP quick_create - agents should prefer exact
interest_pack_namein user-facing flows and keep AdsAgent internal pack ids hidden
For MMP and product reads:
mmp_get_statelists all MMP connections for the current AdsAgent user- one provider can have multiple independent connections; treat
connection_idas the stable operational handle - AppsFlyer metadata, cohort configs, and cohort rows are isolated per
connection_id + provider_app_id products_listreturns saved products plusmmp_match_count,mmp_matches,mmp_events,mmp_event_metrics, and savedfunnel_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_valuesis the per-ad funnel visualization source — returnsfunnel_datakeyed by Metaad_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 andfunnel_data: {}is the correct return, not a bug. For "how many paid today for product X", always usemmp_insights_get_product_event_todayinstead.products_get_funnel_valuesmay also returndata_freshness.labelto describe whether the requested AppsFlyer range isfresh,syncing,stale, ormissingin cache (block also carriesinterval_min+max_staleness_min+last_pulled_atfor context)- when the user asks a question such as "today this MMP event's paid count, paid users, and revenue", call
products_listfirst to resolve the saved product and inspect itsmmp_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 ormmp_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 oneevent_name; it does not mix Meta actions, saved funnel sequencing, or per-ad detail into the responsemmp_insights_get_product_event_todayandmmp_insights_query_product_event_summaryalso accept optionalbreakdown=country | placement | campaign | adset | ad. When breakdown isadsetorad, eachbreakdown_rowsentry also carries anidfield- when
breakdownis set, AdsAgent keeps the same top-levelcount / unique / revenuetotals and adds cleanedbreakdown_rows - REQUIRED
channel_pidparameter onmmp_insights.*: pass one slug likefacebook_int/googleadwords_int/tiktokglobal_intfor that single channel, OR pass'all'to fan out across every channel the user has configured. Omittingchannel_pidreturns aValidationError(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 addsby_channel: [...](alphabetically sorted per-channel rows) +total: {...}(aggregate). For a single slug, the response stays in single-channel shape (noby_channel/totalblock). - 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_cohortsexplicitly only when the user asks for a manual backfill of the local cache (separate from the live read path above) mmp_fetch_cohortsaccepts optionalgrouping_scope; omit it for total cache, usecountryfor country breakdown cache, andplacementfor 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_valueswhen the user explicitly wants the per-ad funnel visualization (dashboard view), NOT for aggregate totals. Emptyfunnel_data: {}from this tool just means no matching ad_id ↔ af_ad_id pairs in the window — route those requests tommp_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_urlcan 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_connectas the source of truth
After authorization:
- The user returns to the agent and says they are done
- The agent calls
setup_check_facebook_connect - 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.
3. Recommended first-run sequence (OAuth Safe Action Mode)
The canonical safe first-run sequence for a normal OAuth user is:
setup_get_statusproducts_list- ask the user which product / date range to inspect
insights_query_overviewfor one requested scope, orinsights_query_batch_overviewfor multiple requested scopes- on Meta only,
setup_begin_facebook_connectwhen Facebook is not connected - on Meta only,
setup_check_facebook_connectafter the user finishes authorization - 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_behaviorwith 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
readyso the agent does not have to infer usability from raw status fieldssync_stageas one ofwaiting_auth,binding,asset_refresh,first_pull,ready,failed, orexpirednext_actionandrecommended_toolfor the next steppoll_after_mswhen the correct action is to keep pollinguser_messageas 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:
mmp_get_statemmp_connectwhen the desired connection does not exist yet — this also auto-matches the user's saved products against the AppsFlyer metadata bystore_url/ bundle idmmp_refresh_connectionwhen synced metadata is staleproducts_listto confirm auto-match succeeded (each product'smmp_match_countshould be ≥ 1 if it was supposed to match)products_link_mmp_appONLY when a product stays atmmp_match_count=0after step 2 — manually attach it to the right(connection_id, provider_app_id)frommmp_get_statemmp_save_cohort_configfor oneconnection_id + provider_app_idmmp_fetch_cohortsfor the desired date range
Important MMP rules:
connection_idis 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_listafter a sync to confirm each saved product is linked, and fall back toproducts_link_mmp_apponly when auto-match missed adjustis stillcoming soon; agents should expectcoming_sooninstead 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_versionper server (Meta and TikTok track independently). When it changes, do a transport-level reconnect (close + reopen the connection — softinitializere-send does NOT refresh the cachedtools/list); then re-list tools and read that server's brief/onboarding guide resource (Meta:adsagent://guide/brief; consult the fulladsagent://guide/toolson demand, never end-to-end). - Prefer
narrativewhen speaking to the user - Prefer
statewhen deciding the next tool call - Prefer
setup_check_facebook_connect.ready,next_action, anduser_messageover 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 defaultvaluewindow and read backeffective_attributionfrom the response. Also chooseview_mode=groupedvsview_mode=dailydeliberately 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,groupedis 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_configandmmp_fetch_cohorts, always keep the chosenconnection_idandprovider_app_idtogether in the same user-facing explanation, and read backgrouping_scopewhen a manual backfill targets country or placement - For pure MMP event-summary reads, resolve the saved product with
products_listfirst, then usemmp_insights_get_product_event_todayfor today's view ormmp_insights_query_product_event_summaryfor a date range. These hit AppsFlyer live so the numbers are always the latest. Only useproducts_get_funnel_valueswhen 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_pidis REQUIRED on everymmp_insights.*call — pass one slug likefacebook_int/googleadwords_int/tiktokglobal_intfor one channel, OR'all'for fan-out +by_channelbreakdown + aggregate total. Omitting it returnsValidationError(the silent-default-to-Facebook behavior was retired in Round 2). - For
products_list, treatmmp_match_count,mmp_matches,mmp_events, andmmp_event_metricsas 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_valuesorinsights_query_overviewreturnscohort_sync, tell the user whether the AppsFlyer cohort range isfresh,syncing,stale, ormissing, and include the date range in the explanation - Prefer public name-based contracts over internal ids:
- use exact
template_namewithtemplates_get/update/deletewhen possible - use exact creative
selection_keywithcreatives_list/get/delete - do not show AdsAgent internal template / creative / folder ids to end users
task_idandconnection_idare the only internal-style ids that should routinely be surfaced to the user- Never assume Facebook connect succeeded until
setup_check_facebook_connectconfirms 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_checkas 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/mcpand 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:
{
"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:
{
"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-intervalwith 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_pidis REQUIRED on everymmp_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_pidREQUIRED onmmp_insights.*platform_user_idper-product scoped (useauth_id/emailcross-product)mapped_events.send_revenuedescribes postback forwarding ≠ cohort completenessselected_channel_pidsis schedule-only, NOT a read-time filterupdated_atis "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.
- Call
templates_listand choose an owned template - Call
creatives_listand choose one or more creativeselection_keyvalues, or collect partnership rows from the user
- if the user wants reusable audience targeting, call
interests_list/getand choose one saved pack by exact name templates_list/templates_getreturn public template contracts without internal template idscreatives_list/creatives_getreturn public creative contracts keyed byselection_key, not internal creative idsinterests_list/interests_getreturn public interest-pack contracts keyed by exactname, not internal AdsAgent ids- for partnership rows, prefer
partnership_ads.quick_createinstead of manually buildingcreative_source.mode=partnership
- Ask whether to publish immediately or tomorrow. For immediate publish, pass
execution.start_mode=immediateor omit it. For tomorrow, passexecution.start_mode=tomorrow; AdsAgent computes account-timezone tomorrow 00:05 like the GUI. - Call
campaigns_quick_create - 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 returnedmissing_requirementsand callcampaigns_quick_createagain withexecution/overridesfilled. Do not guess hidden payload fields. - 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. - Show the returned
approval_request.summaryandapproval_request.warnings - Explain structure using the GUI nesting rule:
- requested structure is per campaign, not flat totals
5 cams, 5 sets, 1 adsmeans 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 adsmeans5 campaigns, 25 ad sets, 25 ads
- Remind the user of the planned campaign names, ad set names, statuses, and publish start label before approval
campaigns_quick_create_confirm(confirm_token)requires explicit user approval — only call it after the user has approved- If the user denies, call
campaigns_quick_create_deny(confirm_token) - After confirm returns
task_id, polltasks_get_status(task_id)untilterminal=true - 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_idtemplate_idortemplate_namecreative_sourceexecution.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_namewhen the user wants one saved global audience pack injected into ad setflexible_spec
Preferred public references:
- prefer exact
template_nameover internal template ids in agent-to-user conversations - prefer exact
interest_pack_nameover 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_idis still allowed because users need it for async follow-up
Optional start input:
execution.start_mode:immediateortomorrow; defaultimmediate
Optional execution status inputs:
execution.campaign_status:ACTIVEorPAUSED; defaultACTIVEexecution.adset_status:ACTIVEorPAUSED; defaultACTIVEexecution.ad_status:ACTIVEorPAUSED; defaultACTIVE
creative_source examples:
{"mode": "creative", "creative_names": ["selection_key_from_creatives_list.png"]}{"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:
partnership_ads.quick_createpartnership_ads.quick_create_confirmpartnership_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_idviaoverrides.page_idtracking_idviaoverrides.tracking_idfor H5 / web templatesstore_urlviaoverrides.store_urllink_urlviaoverrides.link_urloroverrides.store_urlapplication_idin 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:
- Read
task_idfrom confirm result - Call
tasks_get_statuswith thattask_id - If
terminal=false, wait briefly and calltasks_get_statusagain - If
terminal=true, report the finalexecution_summary,result,logs_tail
tasks_get_status.execution_summary includes:
action, usuallyquickad_account_idtemplate_namepublish_start_timepublish_start_label, usuallyImmediateor the scheduled timeresolved_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 moreAllowDeny
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 URLTest MCP URL- one active
MCP tokenslot
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:
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):
- Operator connects (OAuth, or bearer token for legacy clients) and calls
setup_get_status - If blocked on Facebook, operator calls
setup_begin_facebook_connect - User opens the returned connect URL and authorizes Facebook
- User tells the agent:
done - Operator calls
setup_check_facebook_connect - Operator calls
setup_analyze_products - Operator calls
setup_ensure_baseline_templates - Operator calls
setup_readiness_check - 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/mcpadsagent-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 quickstarttiktok-adsagent://guide/onboarding— TikTok-sideagent_gotchaslist + 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/mcpadsagent-google->https://google.adsagent.md/mcpadsagent-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).