diff --git a/src/components/templates/agent-connectors/_section-after-setup-facebookadsmcp-common-workflows.mdx b/src/components/templates/agent-connectors/_section-after-setup-facebookadsmcp-common-workflows.mdx new file mode 100644 index 000000000..d0ade833c --- /dev/null +++ b/src/components/templates/agent-connectors/_section-after-setup-facebookadsmcp-common-workflows.mdx @@ -0,0 +1,78 @@ +{/* TODO: stub cloned from _section-after-setup-atlassianmcp-common-workflows.mdx for Facebook Ads MCP. Review and update connector-specific references (URLs, scopes, app-registration steps) before merging. */} +export const sectionTitle = 'Common workflows' + +import { Tabs, TabItem, Aside } from '@astrojs/starlight/components' + +### Get your cloud ID + +Most Facebook Ads MCP tools require a `cloudId` — the UUID that identifies your Atlassian cloud site. Call `facebookadsmcp_getaccessibleatlassianresources` once to retrieve it, then pass the `id` field value in every subsequent tool call. + + + + + + ```typescript + // Step 1 — get the cloud ID + const resources = await actions.executeTool({ + connectionName: 'facebookadsmcp', + identifier: 'user_123', + toolName: 'facebookadsmcp_getaccessibleatlassianresources', + toolInput: {}, + }); + const cloudId = resources[0].id; + + // Step 2 — use cloudId in subsequent calls + const issue = await actions.executeTool({ + connectionName: 'facebookadsmcp', + identifier: 'user_123', + toolName: 'facebookadsmcp_getjiraissue', + toolInput: { + cloudId, + issueIdOrKey: 'KAN-1', + }, + }); + console.log(issue); + ``` + + + ```python + # Step 1 — get the cloud ID + resources = actions.execute_tool( + connection_name="facebookadsmcp", + identifier="user_123", + tool_name="facebookadsmcp_getaccessibleatlassianresources", + tool_input={}, + ) + cloud_id = resources[0]["id"] + + # Step 2 — use cloud_id in subsequent calls + issue = actions.execute_tool( + connection_name="facebookadsmcp", + identifier="user_123", + tool_name="facebookadsmcp_getjiraissue", + tool_input={ + "cloudId": cloud_id, + "issueIdOrKey": "KAN-1", + }, + ) + print(issue) + ``` + + + +The `facebookadsmcp_getaccessibleatlassianresources` response looks like this: + +```json +[ + { + "id": "a4c9b3e2-1234-5678-abcd-ef0123456789", + "name": "My Company", + "url": "https://mycompany.atlassian.net", + "scopes": ["read:jira-work", "write:jira-work", "read:confluence-content.all"] + } +] +``` + +Use `id` as the `cloudId` parameter. If the user belongs to multiple Atlassian sites, the list contains one entry per site — pick the one matching the target `url`. diff --git a/src/components/templates/agent-connectors/_setup-facebookadsmcp.mdx b/src/components/templates/agent-connectors/_setup-facebookadsmcp.mdx new file mode 100644 index 000000000..8aa34496f --- /dev/null +++ b/src/components/templates/agent-connectors/_setup-facebookadsmcp.mdx @@ -0,0 +1,46 @@ +{/* TODO: stub cloned from _setup-atlassianmcp.mdx for Facebook Ads MCP, then rewritten for bring-your-own OAuth 2.1 with PKCE. Add screenshots and verify provider-specific navigation before merging. */} +import { Steps, Aside } from '@astrojs/starlight/components' + +Facebook Ads MCP uses OAuth 2.1 with PKCE. Meta's Ads MCP server advertises Dynamic Client Registration (DCR) support, but its registration endpoint rejects DCR requests — so you need to register your own OAuth app in Meta's developer console and add the client ID and secret to Scalekit. + + +1. ### Copy the redirect URI from Scalekit + + In the [Scalekit dashboard](https://app.scalekit.com), go to **AgentKit** > **Connections** > **Create Connection**. Find **Facebook Ads MCP** and click **Create**. Copy the redirect URI — it looks like `https:///sso/v1/oauth//callback`. + + {/* TODO: add screenshot — alt: "Copy redirect URI from Scalekit dashboard for Facebook Ads MCP", original src: @/assets/docs/agent-connectors/facebookadsmcp/copy-redirect-uri.png */} + +2. ### Create an OAuth app in Meta for Developers + + {/* TODO: add provider-specific steps — exact navigation for creating a Business app with the Marketing API / Ads Management Standard Access product in the current Meta for Developers console, since Meta's app dashboard changes layout periodically. */} + + - Go to [developers.facebook.com/apps](https://developers.facebook.com/apps) and create (or select) an app with access to the Marketing API. + - Under the app's **Facebook Login for Business** (or equivalent OAuth) settings, add the redirect URI you copied from Scalekit to the list of valid OAuth redirect URIs. + - Request the scopes your agent needs. At minimum: + - `ads_management` — create and manage ad campaigns, ad sets, and ads + - `ads_read` — read ad account, campaign, and reporting data + - `business_management` — manage business assets (ad accounts, catalogs, pages) under a Business Manager + - Depending on your use case, also request: + - `catalog_management` — create and manage product catalogs for ads + - `instagram_basic` — read basic Instagram account info for ad placements + - `pages_show_list` — list Facebook Pages the user manages + - `ads_mcp_management` — MCP-server-specific management scope for the Ads MCP tool surface + + {/* TODO: add screenshot — alt: "Configure OAuth redirect URI and scopes in Meta for Developers", original src: @/assets/docs/agent-connectors/facebookadsmcp/meta-app-oauth-settings.png */} + +3. ### Get the client ID and client secret + + In your Meta app's **Settings** > **Basic** page, copy the **App ID** and **App Secret**. Meta apps typically start in development mode — submit the app for App Review (or add test users) before real customer accounts can authorize it. + + {/* TODO: add screenshot — alt: "App ID and App Secret in Meta app basic settings", original src: @/assets/docs/agent-connectors/facebookadsmcp/meta-app-credentials.png */} + + + +4. ### Add the credentials to Scalekit + + Back in the Scalekit dashboard's **Create Connection** flow for **Facebook Ads MCP**, paste the **App ID** as the client ID and the **App Secret** as the client secret, then save. Scalekit stores these credentials, drives the OAuth 2.1 + PKCE flow, and manages token storage and refresh for every user who authorizes the connection. + + {/* TODO: add screenshot — alt: "Add client ID and client secret for Facebook Ads MCP in Scalekit dashboard", original src: @/assets/docs/agent-connectors/facebookadsmcp/add-credentials-scalekit.png */} + diff --git a/src/components/templates/agent-connectors/index.ts b/src/components/templates/agent-connectors/index.ts index fc4d76aa7..8e263bd1a 100644 --- a/src/components/templates/agent-connectors/index.ts +++ b/src/components/templates/agent-connectors/index.ts @@ -43,6 +43,7 @@ export { default as SetupDropboxSection } from './_setup-dropbox.mdx' export { default as SetupDropboxmcpSection } from './_setup-dropboxmcp.mdx' export { default as SetupExaSection } from './_setup-exa.mdx' export { default as SetupExamcpSection } from './_setup-examcp.mdx' +export { default as SetupFacebookadsmcpSection } from './_setup-facebookadsmcp.mdx' export { default as SetupFathomSection } from './_setup-fathom.mdx' export { default as SetupFellowaimcpSection } from './_setup-fellowaimcp.mdx' export { default as SetupFigmaSection } from './_setup-figma.mdx' @@ -170,6 +171,7 @@ export { default as SectionAfterSetupDiarizeCommonWorkflows } from './_section-a export { default as SectionAfterSetupDiscordCommonWorkflows } from './_section-after-setup-discord-common-workflows.mdx' export { default as SectionAfterSetupDropboxCommonWorkflows } from './_section-after-setup-dropbox-common-workflows.mdx' export { default as SectionAfterSetupExaCommonWorkflows } from './_section-after-setup-exa-common-workflows.mdx' +export { default as SectionAfterSetupFacebookadsmcpCommonWorkflows } from './_section-after-setup-facebookadsmcp-common-workflows.mdx' export { default as SectionAfterSetupFathomCommonWorkflows } from './_section-after-setup-fathom-common-workflows.mdx' export { default as SectionAfterSetupFellowaimcpCommonWorkflows } from './_section-after-setup-fellowaimcp-common-workflows.mdx' export { default as SectionAfterSetupFigmaCommonWorkflows } from './_section-after-setup-figma-common-workflows.mdx' diff --git a/src/content/docs/agentkit/connectors/facebookadsmcp.mdx b/src/content/docs/agentkit/connectors/facebookadsmcp.mdx new file mode 100644 index 000000000..7a7d654c6 --- /dev/null +++ b/src/content/docs/agentkit/connectors/facebookadsmcp.mdx @@ -0,0 +1,89 @@ +--- +title: 'Facebook Ads MCP connector' +tableOfContents: true +description: 'Use Facebook Ads MCP to manage ad campaigns, catalogs, reporting, signals, and A/B tests from your AI agent.' +sidebar: + label: 'Facebook Ads MCP' +overviewTitle: 'Quickstart' +connectorIcon: https://cdn.scalekit.com/sk-connect/assets/provider-icons/facebook.svg +connectorAuthType: OAuth2.1 +connectorCategories: [Marketing, Analytics] +head: + - tag: style + content: | + .sl-markdown-content h2 { + font-size: var(--sl-text-xl); + } + .sl-markdown-content h3 { + font-size: var(--sl-text-lg); + } +--- + +import ToolList from '@/components/ToolList.astro' +import { tools } from '@/data/agent-connectors/facebookadsmcp' +import { Steps, Tabs, TabItem } from '@astrojs/starlight/components' +import { AgentKitCredentials } from '@components/templates' +import { SetupFacebookadsmcpSection } from '@components/templates' +import { QuickstartGenericOauthSection } from '@components/templates' +import { SectionAfterSetupFacebookadsmcpCommonWorkflows } from '@components/templates' + + + +1. ### Install the SDK + + + + ```bash frame="terminal" + npm install @scalekit-sdk/node + ``` + + + ```bash frame="terminal" + pip install scalekit + ``` + + + + Full SDK reference: [Node.js](/agentkit/sdks/node/) | [Python](/agentkit/sdks/python/) + +2. ### Set your credentials + + + +3. ### Set up the connector + + Register your Facebook Ads MCP credentials with Scalekit so it handles the token lifecycle. You do this once per environment. + +
+ Dashboard setup steps + + + +
+ +4. ### Authorize and make your first call + + + +
+ +## What you can do + +Connect this agent connector to let your agent: + +- **Manage campaigns and ads** — create, activate, pause, and update campaigns, ad sets, and ads, including boosting Instagram posts +- **Manage product catalogs** — create and update catalogs, product feeds, and product sets for dynamic and Advantage+ catalog ads +- **Work with signals and datasets** — connect event sources, manage datasets, and check catalog and dynamic ads health and diagnostics +- **Run A/B tests and Conversion Lift studies** — set up and monitor split tests and lift studies to measure ad performance +- **Pull reporting and insights** — fetch account, campaign, and ad-level performance data +- **Review activity logs** — fetch the change history for ad accounts, campaigns, ad sets, and ads + +## Common workflows + + + +## Tool list + +Use the exact tool names from the **Tool list** below when you call `execute_tool`. If you're not sure which name to use, list the tools available for the current user first. + + diff --git a/src/data/agent-connectors/catalog.ts b/src/data/agent-connectors/catalog.ts index 29a0c06a9..9a215149f 100644 --- a/src/data/agent-connectors/catalog.ts +++ b/src/data/agent-connectors/catalog.ts @@ -1700,4 +1700,9 @@ export const catalog: Record = { authType: 'Bearer Token', categories: ['Communication', 'Marketing', 'Developer Tools'], }, + facebookadsmcp: { + iconUrl: 'https://cdn.scalekit.com/sk-connect/assets/provider-icons/facebook.svg', + authType: 'OAuth2.1', + categories: ['Marketing', 'Analytics'], + }, } diff --git a/src/data/agent-connectors/facebookadsmcp.ts b/src/data/agent-connectors/facebookadsmcp.ts new file mode 100644 index 000000000..b4be9e7a4 --- /dev/null +++ b/src/data/agent-connectors/facebookadsmcp.ts @@ -0,0 +1,6394 @@ +import type { Tool } from '../../types/agent-connectors' + +export const tools: Tool[] = [ + { + name: 'facebookadsmcp_ads_account_get_activity_logs', + description: `Fetches activity log entries for an ad account, showing changes made to campaigns, ad sets, ads, and other ad objects. This mirrors the Ads Manager campaign history page, including Meta system-generated changes. + + ## When to use: + - Call this tool when the user asks about changes, modifications, or history of their ad account or specific ad objects. + - Call this tool when the user wants to see what happened in a time range (e.g., "what changed last week?"). + - Call this tool to find out who made specific changes to ad objects. + - Call this tool to investigate budget, status, targeting, or creative changes. + + ## When NOT to use: + - Do NOT use for performance metrics or delivery insights — use ads_insights tools instead. + - Do NOT use for error diagnostics — use ads_get_errors instead. + + ## Response Guidelines: + 1. Present changes in chronological order with actor, event type, and details. + 2. Highlight the most significant changes (status changes, budget modifications). + 3. If extra_data contains old_value/new_value, show what was changed from and to. + 4. Group related changes together when presenting to the user. + + ## Event categories and their event types + The optional \`event_category\` filter accepts exactly one of these categories. Each category maps to the following \`event_type\` values (these are the values that appear in results): + - account: ad_review_approved, ad_review_declined, ad_account_set_business_information, ad_account_update_status, ad_account_add_user_to_role, ad_account_remove_user_from_role + - ad: ad_review_approved, ad_review_declined, add_images, create_ad, edit_images, update_ad_creative, update_ad_friendly_name, update_ad_run_status, update_ad_run_status_to_be_set_after_review + - ad_set: create_ad_set, update_ad_set_bidding, update_ad_set_bid_strategy, update_ad_set_bid_adjustments, update_ad_set_budget, update_ad_set_duration, update_ad_set_name, update_ad_set_run_status, update_ad_set_target_spec, update_ad_set_ad_keywords, conversion_event_updated, update_campaign_schedule, update_ad_set_learning_stage_status, update_campaign_high_demand_periods, update_campaign_budget_scheduling_state, update_campaign_conversion_goal, update_campaign_value_adjustment_rule + - audience: create_audience, update_audience, delete_audience, share_audience, receive_audience, unshare_audience, remove_shared_audience, update_adgroup_stop_delivery, ad_account_update_audience_type_url_parameter, adaccount_update_audience_segment + - bid: update_ad_bid_info, update_ad_bid_type, update_ad_set_bidding, update_ad_set_bid_strategy, update_ad_set_bid_adjustments + - budget: ad_account_billing_charge, ad_account_billing_chargeback, ad_account_billing_chargeback_reversal, ad_account_billing_decline, ad_account_billing_refund, ad_account_remove_spend_limit, ad_account_reset_spend_limit, ad_account_update_spend_limit, add_funding_source, billing_event, funding_event_initiated, funding_event_successful, remove_funding_source, update_ad_set_budget, update_campaign_budget, update_campaign_group_spend_cap, update_budget_flex_toggle_status + - campaign: create_campaign_group, update_campaign_name, update_campaign_run_status, update_campaign_group_high_demand_periods, update_campaign_group_budget_scheduling_state + - date: update_ad_set_duration + - status: ad_account_update_status, update_ad_run_status, update_ad_run_status_to_be_set_after_review, update_ad_set_run_status, update_campaign_run_status + - targeting: update_ad_set_target_spec, update_ad_targets_spec + - ad_keywords: update_ad_set_ad_keywords`, + params: [ + { + name: 'ad_account_id', + type: 'string', + required: true, + description: `The ad account ID (numeric, without "act_" prefix).`, + }, + { + name: 'advertiser_request', + type: 'string', + required: false, + description: `Capture what the advertiser is actually asking for, in their exact words, quoted from their own messages word for word wherever you can. A question, lookup, or check counts as a real request (for example 'do I have X listed?', 'show me my Y', 'how many Z are left?'), so capture it. Pull their request from anywhere in the conversation, including across multiple turns: if they state a goal or problem early and then approve or narrow an action later (for example a brief 'yes, go ahead'), combine these into one request that keeps the action and its original subject and conditions, and capture that earlier request, not the bare confirmation. If they ask for several things, include every part, not just the one this tool handles. Stay strictly in the advertiser's own vocabulary: do not paraphrase, summarize, or shift their words into a more formal register; do not upgrade their plain words into domain or industry terms; do not phrase the request as the command or operation this tool performs; and do not add metric abbreviations or technical, product, or system field names they did not say themselves. This holds in every language: keep their phrasing in the language they used and never substitute the technical equivalent, translated or not, unless the advertiser used that term themselves. Leave this empty only when the message is pure greeting, small talk, thanks, or acknowledgment with no request of any kind; do not invent or infer a request that was not expressed. Do not include names, contact details, or other personal information.`, + }, + { + name: 'end_time', + type: 'string', + required: false, + description: `Optional. End time in ISO 8601 format. Defaults to now.`, + }, + { + name: 'event_category', + type: 'string', + required: false, + description: `Optional. Filter by event category. Values: account, ad, ad_set, audience, bid, budget, campaign, date, status, targeting, ad_keywords.`, + }, + { + name: 'limit', + type: 'integer', + required: false, + description: `Optional. Maximum number of results to return (1-1000). Defaults to 100.`, + }, + { + name: 'object_id', + type: 'string', + required: false, + description: `Optional. Filter to a specific ad object ID (campaign, ad set, ad, creative). Selecting a campaign or ad set also includes its descendants (ad sets and ads), matching the Ads Manager history page.`, + }, + { + name: 'start_time', + type: 'string', + required: false, + description: `Optional. Start time in ISO 8601 format (e.g., "2025-01-01T00:00:00Z"). Defaults to 3 months ago.`, + }, + { + name: 'user_id', + type: 'string', + required: false, + description: `Optional. Filter by the user who made the change.`, + }, + ], + }, + { + name: 'facebookadsmcp_ads_activate_entity', + description: `Activates a campaign, ad set, or ad by changing its status from PAUSED to ACTIVE, effectively publishing the entity. + + ## When to use: + - Call this tool ONLY after the user has explicitly confirmed they want to publish/activate. + - Use after the user has reviewed the entity via ads_create_campaign or ads_update_entity. + - Supports entity_type values: "campaign", "ad_set", or "ad". + + ## When NOT to use: + - Do NOT call without explicit user confirmation to publish. + - Do NOT use for pausing active entities. Use ads_update_entity with status field instead. + + ## Status Hierarchy: + - Activating a parent does NOT automatically activate its children. Each entity must be activated individually. + - For ads to deliver, ALL levels in the hierarchy must be ACTIVE (campaign, ad set, and ad). + - Activating a child entity while its parent is paused will succeed, but the child will NOT deliver until the parent is also activated. + - After creating a full campaign structure, activate from top to bottom: campaign first, then ad set, then ad. + + ## Response Guidelines: + 1. For live entities, confirm the entity has been activated. + 2. A PUBLISHING status means the draft campaign passed validation and was handed + to the publisher, which finishes after this call returns. Report it as in + progress; do NOT tell the user the campaign is live. + 3. If the call is rejected for validation errors, the message lists the offending + campaign, ad set, or ad and its errors. Nothing was published. Relay the + errors, fix them with ads_update_entity, then activate again. + 4. Provide the entity ID, entity type, and new status. + 5. If activating a child entity, remind the user that all parent entities must also be active for ads to deliver. + + ## CRITICAL: + - This action makes the entity live and will start spending budget. + - Always get explicit user confirmation before calling this tool. + + ## POTENTIAL NEXT STEP — RECOMMEND OPPORTUNITY SCORE: + After a successful activation, suggest calling \`ads_get_opportunity_score\` to check + the account's optimization status and get personalized recommendations + from Meta to maximize ad performance while spending is active.`, + params: [ + { + name: 'ad_account_id', + type: 'string', + required: true, + description: `The ad account ID that owns the entity.`, + }, + { + name: 'entity_id', + type: 'string', + required: true, + description: `The entity ID to activate (change from PAUSED to ACTIVE).`, + }, + { + name: 'entity_type', + type: 'string', + required: true, + description: `The type of entity to activate. Values: campaign, ad_set, ad.`, + }, + { + name: 'advertiser_request', + type: 'string', + required: false, + description: `Capture what the advertiser is actually asking for, in their exact words, quoted from their own messages word for word wherever you can. A question, lookup, or check counts as a real request (for example 'do I have X listed?', 'show me my Y', 'how many Z are left?'), so capture it. Pull their request from anywhere in the conversation, including across multiple turns: if they state a goal or problem early and then approve or narrow an action later (for example a brief 'yes, go ahead'), combine these into one request that keeps the action and its original subject and conditions, and capture that earlier request, not the bare confirmation. If they ask for several things, include every part, not just the one this tool handles. Stay strictly in the advertiser's own vocabulary: do not paraphrase, summarize, or shift their words into a more formal register; do not upgrade their plain words into domain or industry terms; do not phrase the request as the command or operation this tool performs; and do not add metric abbreviations or technical, product, or system field names they did not say themselves. This holds in every language: keep their phrasing in the language they used and never substitute the technical equivalent, translated or not, unless the advertiser used that term themselves. Leave this empty only when the message is pure greeting, small talk, thanks, or acknowledgment with no request of any kind; do not invent or infer a request that was not expressed. Do not include names, contact details, or other personal information.`, + }, + ], + }, + { + name: 'facebookadsmcp_ads_boost_ig_post', + description: `Creates an Instagram ad from an existing IG post. Supports a plan/confirm two-step flow. + + Only 3 fields are required: ad_account_id, ig_account_id, ig_media_id. All other fields are optional with sensible defaults. For simple boosts, just provide the required fields. For full control, override any campaign (L3), ad set (L2), or ad (L1) field. + + ## When to use: + - Call with confirmed=false FIRST to get a plan showing all resolved settings. + - Call with confirmed=true ONLY after the user has reviewed and approved the plan. + - Use after ads_get_ig_accounts and ads_get_ig_media to get the required IDs. + + ## When NOT to use: + - Do NOT call with confirmed=true without first showing the user the plan. + - Do NOT use for creating regular (non-IG-boost) ad campaigns — use ads_create_campaign instead. + + ## Response Guidelines: + 1. In plan mode: Show all resolved settings across L3/L2/L1, including defaults. + 2. In creation mode: Return all created entity IDs and confirm PAUSED state. + 3. Remind user they can activate with ads_activate_entity when ready. + + ## CRITICAL: + - All entities are created in PAUSED state. Budget is NOT spent until activated. + - Always show the plan first and get explicit user confirmation before creating. + - The ig_account_id must be linked to the ad_account_id and the app must have \`instagram_basic\` permission for it. Use ads_get_ig_accounts to find eligible accounts. + - Defaults: roughly the $5 USD/day-equivalent in the ad account's local currency for 6 days, OUTCOME_TRAFFIC objective, INSTAGRAM_PROFILE destination, US targeting, IMPRESSIONS billing, LOWEST_COST_WITHOUT_CAP bidding. + - All budget amounts are in the smallest unit of the ad account's currency (e.g. cents for USD, whole yen for JPY). The plan output includes a \`currency\` field — present budgets in that currency, never assume USD.`, + params: [ + { + name: 'ad_account_id', + type: 'string', + required: true, + description: `The ad account ID to create the boost under. Format: numeric ID.`, + }, + { + name: 'ig_account_id', + type: 'string', + required: true, + description: `The IG account ID that owns the media to boost.`, + }, + { + name: 'ig_media_id', + type: 'string', + required: true, + description: `The numeric id field from ads_get_ig_media output. Do NOT decode or transform the permalink URL shortcode — use the id value exactly as returned.`, + }, + { + name: 'ad_name', + type: 'string', + required: false, + description: `Name for the ad. Default: "Instagram post: {caption}" derived from the IG post.`, + }, + { + name: 'ad_set_name', + type: 'string', + required: false, + description: `Name for the ad set. Default: "Instagram post: {caption}" derived from the IG post.`, + }, + { + name: 'advertiser_request', + type: 'string', + required: false, + description: `Capture what the advertiser is actually asking for, in their exact words, quoted from their own messages word for word wherever you can. A question, lookup, or check counts as a real request (for example 'do I have X listed?', 'show me my Y', 'how many Z are left?'), so capture it. Pull their request from anywhere in the conversation, including across multiple turns: if they state a goal or problem early and then approve or narrow an action later (for example a brief 'yes, go ahead'), combine these into one request that keeps the action and its original subject and conditions, and capture that earlier request, not the bare confirmation. If they ask for several things, include every part, not just the one this tool handles. Stay strictly in the advertiser's own vocabulary: do not paraphrase, summarize, or shift their words into a more formal register; do not upgrade their plain words into domain or industry terms; do not phrase the request as the command or operation this tool performs; and do not add metric abbreviations or technical, product, or system field names they did not say themselves. This holds in every language: keep their phrasing in the language they used and never substitute the technical equivalent, translated or not, unless the advertiser used that term themselves. Leave this empty only when the message is pure greeting, small talk, thanks, or acknowledgment with no request of any kind; do not invent or infer a request that was not expressed. Do not include names, contact details, or other personal information.`, + }, + { + name: 'bid_amount', + type: 'integer', + required: false, + description: `Bid cap in the smallest unit of the ad account currency (e.g. cents for USD, whole yen for JPY). Used with LOWEST_COST_WITH_BID_CAP bid strategy.`, + }, + { + name: 'bid_strategy', + type: 'string', + required: false, + description: `Ad set bid strategy. Default: LOWEST_COST_WITHOUT_CAP. Values: LOWEST_COST_WITHOUT_CAP, LOWEST_COST_WITH_BID_CAP, COST_CAP.`, + }, + { + name: 'billing_event', + type: 'string', + required: false, + description: `Ad set billing event. Default: IMPRESSIONS. Values: IMPRESSIONS, LINK_CLICKS, POST_ENGAGEMENT, VIDEO_VIEWS.`, + }, + { + name: 'buying_type', + type: 'string', + required: false, + description: `Campaign buying type. Default: AUCTION. Values: AUCTION, RESERVED.`, + }, + { + name: 'call_to_action', + type: 'string', + required: false, + description: `Call-to-action for the ad creative.`, + }, + { + name: 'campaign_bid_strategy', + type: 'string', + required: false, + description: `Campaign-level bid strategy. Values: LOWEST_COST_WITHOUT_CAP, LOWEST_COST_WITH_BID_CAP, COST_CAP.`, + }, + { + name: 'campaign_daily_budget', + type: 'integer', + required: false, + description: `Campaign-level daily budget in the smallest unit of the ad account currency (e.g. cents for USD, whole yen for JPY). Mutually exclusive with campaign_lifetime_budget.`, + }, + { + name: 'campaign_lifetime_budget', + type: 'integer', + required: false, + description: `Campaign-level lifetime budget in the smallest unit of the ad account currency (e.g. cents for USD, whole yen for JPY). Mutually exclusive with campaign_daily_budget.`, + }, + { + name: 'campaign_name', + type: 'string', + required: false, + description: `Name for the campaign. Default: "Instagram post: {caption}" derived from the IG post.`, + }, + { + name: 'confirmed', + type: 'boolean', + required: false, + description: `Set to true to create the ad. Set to false (default) to get a plan showing resolved settings without creating anything.`, + }, + { + name: 'daily_budget', + type: 'integer', + required: false, + description: `Daily budget in the smallest unit of the ad account currency (e.g. cents for USD, whole yen for JPY). Defaults to roughly the $5 USD/day-equivalent in the account currency, clamped to the per-currency minimum. Mutually exclusive with lifetime_budget.`, + }, + { + name: 'destination_type', + type: 'string', + required: false, + description: `Ad set destination type. Default: INSTAGRAM_PROFILE. Values: INSTAGRAM_PROFILE, WEBSITE, ON_AD, INSTAGRAM_DIRECT, MESSENGER, WHATSAPP, FACEBOOK, SHOP_AUTOMATIC.`, + }, + { + name: 'duration_days', + type: 'integer', + required: false, + description: `Number of days to run the ad. Default: 6.`, + }, + { + name: 'end_time', + type: 'string', + required: false, + description: `Ad set end time in ISO 8601 format. Default: computed from duration_days.`, + }, + { + name: 'lifetime_budget', + type: 'integer', + required: false, + description: `Ad set lifetime budget in the smallest unit of the ad account currency (e.g. cents for USD, whole yen for JPY). Mutually exclusive with daily_budget. Requires end_time.`, + }, + { + name: 'objective', + type: 'string', + required: false, + description: `Campaign objective. Default: OUTCOME_TRAFFIC. Only ODAX outcome values are accepted: OUTCOME_AWARENESS, OUTCOME_TRAFFIC, OUTCOME_ENGAGEMENT, OUTCOME_LEADS, OUTCOME_SALES, OUTCOME_APP_PROMOTION.`, + }, + { + name: 'optimization_goal', + type: 'string', + required: false, + description: `Ad set optimization goal. Default: VISIT_INSTAGRAM_PROFILE. Values: VISIT_INSTAGRAM_PROFILE, LINK_CLICKS, POST_ENGAGEMENT, CONVERSATIONS, LEAD_GENERATION, OFFSITE_CONVERSIONS, REACH, IMPRESSIONS, LANDING_PAGE_VIEWS.`, + }, + { + name: 'promoted_object', + type: 'string', + required: false, + description: `JSON promoted object spec for conversion tracking. Required for OUTCOME_SALES/OUTCOME_LEADS. Example: {"pixel_id":"123456","custom_event_type":"PURCHASE"}.`, + }, + { + name: 'special_ad_categories', + type: 'string', + required: false, + description: `JSON array of special ad categories. Default: []. Values: CREDIT, EMPLOYMENT, HOUSING, ISSUES_ELECTIONS_POLITICS.`, + }, + { + name: 'start_time', + type: 'string', + required: false, + description: `Ad set start time in ISO 8601 format. Default: immediate.`, + }, + { name: 'targeting', type: 'string', required: false, description: `JSON targeting spec.` }, + ], + }, + { + name: 'facebookadsmcp_ads_catalog_create', + description: `Creates a new product catalog for a business and uploads product data in one step, using a feed URL, inline batch items, or a direct file upload. + +## Always Required (the call fails without these): +- \`catalog_name\`: the name for the new catalog. If the user has not given a name, ask them for one before calling this tool — do NOT invent a placeholder and do NOT call without it. +- \`business_id\`: the Meta Business Manager ID that will own the catalog. This must be a Business Manager ID, NOT an ad account ID and NOT a Page ID. If you only have an ad account ID or are unsure, call ads_catalog_get_catalogs (or ask the user) to find the correct Business Manager ID first — passing the wrong ID type fails with "not a valid business_id". + +This tool ALWAYS uploads product data as part of catalog creation — it cannot create an empty catalog. In addition to the two always-required arguments above, you MUST provide exactly one data source (feed_url, items, or feed_file_content); the call fails if you provide none, or more than one. + +## IMPORTANT — One Catalog Guidance: +Before calling this tool, ALWAYS call ads_catalog_get_catalogs with the business_id to check for existing catalogs. Businesses should use a single catalog for all advertising and commerce objectives. Creating duplicate catalogs with overlapping items causes: +- Signal fragmentation (up to 14% lower ROAS) +- Selection liquidity loss (up to 18% higher CPA) +- Cold start problems (up to 21% performance reduction) +- Metadata fragmentation across catalogs +If the business already has catalogs, strongly recommend using the existing catalog instead. Only proceed with creation after confirming with the user that a new catalog with distinct items is needed. + +## When to use: +- User wants to create a new catalog and upload product data from scratch. +- User says "set up a new catalog", "onboard my products", or "create a catalog with my feed." +- User has a feed URL (CSV/TSV/XML) or product items to upload. + +## When NOT to use: +- Do NOT call this tool to modify an existing catalog or add a feed to an existing catalog. +- Do NOT call this tool if the user just wants to list or inspect catalogs — use ads_catalog_get_catalogs or ads_catalog_get_details instead. +- Do NOT call this tool without first checking for existing catalogs via ads_catalog_get_catalogs. + +## Data Upload Methods (exactly one required): +1. **Feed URL**: Provide feed_url (+ optional feed_name, schedule). Meta fetches data from the URL. +2. **Batch items**: Provide items array with product data inline. Items are uploaded via Batch API. +3. **File upload**: Provide feed_file_content (base64-encoded file, + feed_file_name and optional feed_file_type). Meta ingests the uploaded file directly. + +## Input Requirements: +- \`business_id\` (always required): The Meta Business Manager ID to create the catalog under. Must be a Business Manager ID, not an ad account ID or Page ID. +- \`catalog_name\` (always required): Name for the new catalog. Ask the user if it was not provided. +- \`vertical\` (optional): Catalog vertical, defaults to "commerce". +- \`feed_name\` (optional): Name for the feed. Required when using feed_url. +- \`feed_url\` (optional): URL of the product data file (CSV/TSV/XML). Mutually exclusive with items and feed_file_content. +- \`schedule\` (optional): Feed schedule when using feed_url. Object with interval, hour, minute, timezone, day_of_week. +- \`items\` (optional): Array of product items for Batch API upload. Mutually exclusive with feed_url and feed_file_content. +- \`feed_file_content\` (optional): Base64-encoded file content for direct upload. Mutually exclusive with feed_url and items. Requires feed_file_name. +- \`feed_file_name\` (optional): Original filename for the uploaded file (e.g., "products.csv"). Required when feed_file_content is provided. +- \`feed_file_type\` (optional): MIME type of the uploaded file. Defaults to "text/csv". + +## Output Format: +JSON with catalog_id, catalog_name, upload_method ("feed" or "batch"), and conditionally feed_id/feed_name (feed path) or batch_handles (batch path).`, + params: [ + { + name: 'business_id', + type: 'string', + required: true, + description: `Always required. The Meta Business Manager ID to create the catalog under. This must be a Business Manager ID, NOT an ad account ID and NOT a Page ID. Look it up with ads_catalog_get_catalogs or in Meta Business Settings if unknown.`, + }, + { + name: 'catalog_name', + type: 'string', + required: true, + description: `Always required. Name for the new catalog. If the user did not specify one, ask them for it instead of calling this tool without it.`, + }, + { + name: 'advertiser_request', + type: 'string', + required: false, + description: `Capture what the advertiser is actually asking for, in their exact words, quoted from their own messages word for word wherever you can. A question, lookup, or check counts as a real request (for example 'do I have X listed?', 'show me my Y', 'how many Z are left?'), so capture it. Pull their request from anywhere in the conversation, including across multiple turns: if they state a goal or problem early and then approve or narrow an action later (for example a brief 'yes, go ahead'), combine these into one request that keeps the action and its original subject and conditions, and capture that earlier request, not the bare confirmation. If they ask for several things, include every part, not just the one this tool handles. Stay strictly in the advertiser's own vocabulary: do not paraphrase, summarize, or shift their words into a more formal register; do not upgrade their plain words into domain or industry terms; do not phrase the request as the command or operation this tool performs; and do not add metric abbreviations or technical, product, or system field names they did not say themselves. This holds in every language: keep their phrasing in the language they used and never substitute the technical equivalent, translated or not, unless the advertiser used that term themselves. Leave this empty only when the message is pure greeting, small talk, thanks, or acknowledgment with no request of any kind; do not invent or infer a request that was not expressed. Do not include names, contact details, or other personal information.`, + }, + { + name: 'feed_file_content', + type: 'string', + required: false, + description: `Base64-encoded file content for direct file upload. Mutually exclusive with feed_url and items. Practical limit ~10MB before encoding.`, + }, + { + name: 'feed_file_name', + type: 'string', + required: false, + description: `Original filename for the uploaded file (e.g., "products.csv"). Required when feed_file_content is provided.`, + }, + { + name: 'feed_file_type', + type: 'string', + required: false, + description: `MIME type of the uploaded file. Defaults to "text/csv". Examples: "text/csv", "text/tab-separated-values", "application/xml".`, + }, + { + name: 'feed_name', + type: 'string', + required: false, + description: `Name for the product feed. Required when using feed_url.`, + }, + { + name: 'feed_password', + type: 'string', + required: false, + description: `Password for authenticated feed URL access (HTTP basic auth or SFTP). Only used with feed_url.`, + }, + { + name: 'feed_url', + type: 'string', + required: false, + description: `URL of the product data file (CSV/TSV/XML). Mutually exclusive with items and feed_file_content.`, + }, + { + name: 'feed_username', + type: 'string', + required: false, + description: `Username for authenticated feed URL access (HTTP basic auth or SFTP). Only used with feed_url.`, + }, + { + name: 'items', + type: 'array', + required: false, + description: `Product items for Batch API upload. Mutually exclusive with feed_url and feed_file_content.`, + }, + { + name: 'schedule', + type: 'object', + required: false, + description: `Feed schedule configuration. Only used with feed_url.`, + }, + { + name: 'update_only', + type: 'boolean', + required: false, + description: `If true, the upload only updates existing items instead of creating new ones. Defaults to false.`, + }, + { + name: 'vertical', + type: 'string', + required: false, + description: `Catalog vertical (e.g., commerce, vehicles, hotels). Defaults to commerce.`, + }, + ], + }, + { + name: 'facebookadsmcp_ads_catalog_create_feed_rule', + description: `Create a new transformation rule on a product feed. Feed rules map or transform attributes during ingestion to fix common feed schema mismatches without the advertiser editing their source file. + +When to use: +- The advertiser's feed column name does not match a Meta product attribute (use mapping_rule, e.g. params={"map_from": "desc"} on attribute="description"). +- A specific value needs translation (use value_mapping_rule, e.g. map "yes"/"no" availability values to "in stock"/"out of stock"). +- Letter case needs normalization (use letter_case_rule with type=to_upper|to_lower|capitalize_all|capitalize_first). +- A default value should be filled in when the feed leaves a column empty (use fallback_rule with user_default_value). +- A regex find/replace is needed on values (use regex_replace_rule). + +When NOT to use: +- To inspect existing rules on a feed (use ads_catalog_get_feed_rules). +- To upload or modify feed data itself (use the catalog ingestion endpoints). +- To create a catalog, feed, or product set (use ads_catalog_create for a catalog, ads_catalog_create_product_feed for a feed, ads_catalog_create_product_set for a product set). + +Notes: +- params is a JSON-encoded object of string-to-string key-value pairs, e.g. {"map_from": "desc"}. +- Both attribute and rule_type are immutable after creation; only params can be updated later via a separate endpoint. +- The combination (product_feed_id, rule_type, attribute) must be unique. Re-creating the same combination returns a duplicate error.`, + params: [ + { + name: 'attribute', + type: 'string', + required: true, + description: `The catalog attribute the rule transforms (e.g., "description", "availability", "title", "price"). A feed cannot have more than one rule with the same rule_type and attribute. Immutable once created.`, + }, + { + name: 'product_feed_id', + type: 'string', + required: true, + description: `The ID of the product feed to attach this rule to (numeric string).`, + }, + { + name: 'rule_type', + type: 'string', + required: true, + description: `The type of rule. One of: "mapping_rule" (map a source column to a catalog attribute, e.g. params={"map_from": "desc"}); "value_mapping_rule" (map raw input values to catalog values for a specific attribute); "letter_case_rule" (transform letter case; params={"type": "to_upper|to_lower|capitalize_all|capitalize_first"}); "fallback_rule" (use params={"user_default_value": "..."} when the input value is empty); "regex_replace_rule" (regex-based find/replace on values). Immutable once created.`, + }, + { + name: 'advertiser_request', + type: 'string', + required: false, + description: `Capture what the advertiser is actually asking for, in their exact words, quoted from their own messages word for word wherever you can. A question, lookup, or check counts as a real request (for example 'do I have X listed?', 'show me my Y', 'how many Z are left?'), so capture it. Pull their request from anywhere in the conversation, including across multiple turns: if they state a goal or problem early and then approve or narrow an action later (for example a brief 'yes, go ahead'), combine these into one request that keeps the action and its original subject and conditions, and capture that earlier request, not the bare confirmation. If they ask for several things, include every part, not just the one this tool handles. Stay strictly in the advertiser's own vocabulary: do not paraphrase, summarize, or shift their words into a more formal register; do not upgrade their plain words into domain or industry terms; do not phrase the request as the command or operation this tool performs; and do not add metric abbreviations or technical, product, or system field names they did not say themselves. This holds in every language: keep their phrasing in the language they used and never substitute the technical equivalent, translated or not, unless the advertiser used that term themselves. Leave this empty only when the message is pure greeting, small talk, thanks, or acknowledgment with no request of any kind; do not invent or infer a request that was not expressed. Do not include names, contact details, or other personal information.`, + }, + { + name: 'params', + type: 'string', + required: false, + description: `Optional JSON-encoded object of string-to-string key-value parameters for the rule, e.g. {"map_from": "desc"} for a mapping_rule or {"type": "to_lower"} for a letter_case_rule. Common keys: "map_from" (mapping_rule source column), "type" (letter_case_rule mode), "user_default_value" (fallback_rule default), "dependent_field_name", "dependent_field_value", "map_mode" (COPY|MOVE). String values are stored verbatim (whitespace preserved). If omitted, defaults to an empty object.`, + }, + ], + }, + { + name: 'facebookadsmcp_ads_catalog_create_product_feed', + description: `Create a new product feed (a "data source") under a catalog. A product feed is the entry point for ingesting product data into a catalog, either by uploading a file or by pointing Meta at a URL to fetch on a recurring schedule. This complements ads_catalog_create, which creates the catalog itself. + +When to use: +- The advertiser wants to add a new source of products to an existing catalog. +- The advertiser wants Meta to fetch a feed file (CSV/TSV/XML) from a URL on a recurring schedule (pass the schedule object). +- Setting up a feed before uploading items or attaching feed rules. + +When NOT to use: +- To create the catalog itself (use ads_catalog_create). +- To upload items into an existing feed right now (use the catalog ingestion / upload-session endpoints). +- To add transformation rules to an existing feed (use ads_catalog_create_feed_rule). +- To inspect existing feeds (use ads_catalog_get_product_feed_details). + +Notes: +- schedule is optional. When omitted, the feed is created with no automatic fetch schedule. When provided, it is a structured object; "interval" and "url" are required and (S)FTP URLs additionally require "username"/"password". +- If the feed is created successfully but the schedule fails to attach, use ads_catalog_update_product_feed with replace_schedule to add the schedule to the existing feed. Do not create a duplicate feed. +- The feed is created under the catalog identified by catalog_id; the output id is the new feed's Meta-assigned ID. +- Requires EDIT_PRODUCT_CATALOG permission on the catalog.`, + params: [ + { + name: 'catalog_id', + type: 'string', + required: true, + description: `The ID of the catalog to create the product feed under (numeric string).`, + }, + { + name: 'name', + type: 'string', + required: true, + description: `A human-readable name for the product feed (e.g. "Summer 2026 Catalog Feed").`, + }, + { + name: 'advertiser_request', + type: 'string', + required: false, + description: `Capture what the advertiser is actually asking for, in their exact words, quoted from their own messages word for word wherever you can. A question, lookup, or check counts as a real request (for example 'do I have X listed?', 'show me my Y', 'how many Z are left?'), so capture it. Pull their request from anywhere in the conversation, including across multiple turns: if they state a goal or problem early and then approve or narrow an action later (for example a brief 'yes, go ahead'), combine these into one request that keeps the action and its original subject and conditions, and capture that earlier request, not the bare confirmation. If they ask for several things, include every part, not just the one this tool handles. Stay strictly in the advertiser's own vocabulary: do not paraphrase, summarize, or shift their words into a more formal register; do not upgrade their plain words into domain or industry terms; do not phrase the request as the command or operation this tool performs; and do not add metric abbreviations or technical, product, or system field names they did not say themselves. This holds in every language: keep their phrasing in the language they used and never substitute the technical equivalent, translated or not, unless the advertiser used that term themselves. Leave this empty only when the message is pure greeting, small talk, thanks, or acknowledgment with no request of any kind; do not invent or infer a request that was not expressed. Do not include names, contact details, or other personal information.`, + }, + { + name: 'country', + type: 'string', + required: false, + description: `Optional ISO 3166-1 alpha-2 country code for the feed (e.g. "US", "GB"). Defaults to "US" when omitted.`, + }, + { + name: 'default_currency', + type: 'string', + required: false, + description: `Optional ISO 4217 currency code used for items in the feed that do not specify their own currency (e.g. "USD", "GBP"). Defaults to "USD" when omitted.`, + }, + { + name: 'feed_type', + type: 'string', + required: false, + description: `Optional feed type. Most commerce/product catalogs can omit this to use the catalog's default item type. Use lowercase feed type values such as "products" (the default), "hotel", "flight", "destination", "home_listing", "vehicles", or "media_title". Uppercase aliases are accepted and normalized. Must be valid for the catalog's vertical.`, + }, + { + name: 'schedule', + type: 'object', + required: false, + description: `Optional recurring schedule for fetching the feed from a URL. Omit it to create a feed with no automatic fetch schedule (items can still be uploaded manually). When provided, "interval" and "url" are required, and (S)FTP URLs additionally require "username"/"password".`, + }, + ], + }, + { + name: 'facebookadsmcp_ads_catalog_create_product_feed_upload_session', + description: `Triggers a new upload session on an existing product feed, forcing an immediate refresh from the configured remote source URL. Use this when an agent or advertiser needs to pull the latest products without waiting for the next scheduled run. Returns the new upload session ID so the caller can poll for status via \`ads_catalog_get_product_feed_upload_sessions\`. + +This tool only works for feeds that have a remote fetch URL configured. Before calling it, check the feed's configuration with \`ads_catalog_get_product_feed_details\` — if the feed has no source URL, this tool cannot start an upload session. + +## When to use: +- The user wants to refresh / re-pull / re-ingest products from an existing remote feed without waiting for the next scheduled run. +- The user is debugging ingestion and wants to force a fresh fetch (e.g., after fixing the source file at the merchant URL). +- The user has updated their hosted feed file and wants the catalog to reflect the change immediately. + +## When NOT to use: +- The feed has no configured source URL (i.e., the catalog was populated by manual file upload or batch API only) — verify this first with \`ads_catalog_get_product_feed_details\`, then use \`ads_catalog_update_product_feed\` with \`replace_schedule\` to add a fetch schedule and source URL before triggering an upload. +- The user wants to create a new feed — use \`ads_catalog_create_product_feed\` instead. +- The user wants to inspect previous upload runs — use \`ads_catalog_get_product_feed_upload_sessions\`. +- The user wants to change the feed's schedule, URL, or credentials — use \`ads_catalog_update_product_feed\` instead. + +## Input Requirements: +- \`product_feed_id\` (required): The ID of the product feed to refresh. + +## Output Format: +JSON with: +- \`upload_session_id\` — the newly created session ID. +- \`product_feed_id\` — echo of the input feed ID. +- \`source_url\` — the URL the upload will fetch from (no credentials). + +## Errors: +- If the feed has no configured source URL, the tool fails because there is nothing to fetch from. Check the feed with \`ads_catalog_get_product_feed_details\` first; a feed without a source URL cannot trigger an upload session. +- If another upload for the same feed is already running, the tool fails with an "upload already in progress" error. Concurrent uploads are not supported — do NOT retry immediately. Wait for the current session to finish (poll with \`ads_catalog_get_product_feed_upload_sessions\`) or inform the user that an upload is already underway.`, + params: [ + { + name: 'product_feed_id', + type: 'string', + required: true, + description: `The ID of the product feed to refresh. The feed must be a remote feed with a configured source URL.`, + }, + { + name: 'advertiser_request', + type: 'string', + required: false, + description: `Capture what the advertiser is actually asking for, in their exact words, quoted from their own messages word for word wherever you can. A question, lookup, or check counts as a real request (for example 'do I have X listed?', 'show me my Y', 'how many Z are left?'), so capture it. Pull their request from anywhere in the conversation, including across multiple turns: if they state a goal or problem early and then approve or narrow an action later (for example a brief 'yes, go ahead'), combine these into one request that keeps the action and its original subject and conditions, and capture that earlier request, not the bare confirmation. If they ask for several things, include every part, not just the one this tool handles. Stay strictly in the advertiser's own vocabulary: do not paraphrase, summarize, or shift their words into a more formal register; do not upgrade their plain words into domain or industry terms; do not phrase the request as the command or operation this tool performs; and do not add metric abbreviations or technical, product, or system field names they did not say themselves. This holds in every language: keep their phrasing in the language they used and never substitute the technical equivalent, translated or not, unless the advertiser used that term themselves. Leave this empty only when the message is pure greeting, small talk, thanks, or acknowledgment with no request of any kind; do not invent or infer a request that was not expressed. Do not include names, contact details, or other personal information.`, + }, + ], + }, + { + name: 'facebookadsmcp_ads_catalog_create_product_set', + description: `Creates a dynamic product set in a catalog from a structured filter rule (see **Filter spec** below) and returns the new set's ID, name, filter, and the number of products that match. Always preview the filter first with \`ads_catalog_search_product\` and confirm with the user before calling this tool — set creation mutates the catalog and there is no undo via this tool. + +## When to use: +- The user has explicitly confirmed they want to create a product set from a filter you've already previewed via \`ads_catalog_search_product\`. +- The user wants to materialize a catalog segment that the structured product-set creation flows do not support (compound AND/OR/NOT, range queries on custom fields). + +## When NOT to use: +- The user has not yet confirmed the filter — call \`ads_catalog_search_product\` first and surface the sample products + total_count. +- The user wants to create a static set of explicit product IDs — out of scope for v1; tell the user this is not yet supported. +- The user wants to update an existing set — use \`ads_catalog_update_product_set\` instead. + +## Input: +- \`catalog_id\` (required): the product catalog ID to create the set under. +- \`title\` (required): display name for the new set. Trimmed; must be non-empty. +- \`filter\` (required): a JSON-encoded rule selecting which products belong to the set. Same operators, same field list, same vertical/item-type validation as \`ads_catalog_search_product\`. See the **Filter spec** section below for the full operator catalog and examples. +- \`retailer_id\` (optional): a merchant-assigned identifier for the set. Only pass when the user provides one explicitly. + +## Returns: +JSON with the same shape as a single entry from \`ads_catalog_get_product_sets\`: +- \`product_set_id\`, \`catalog_id\`, \`name\`, \`product_count\` (matched-item count at creation time), \`retailer_id\`, \`filter_rule\`, \`product_set_type\`, \`visibility\`, \`creation_time\`. + +## Errors: +- Malformed filter JSON, or a filter that references a field not valid for the catalog's vertical, is rejected before any mutation with an actionable error. +- Duplicate sets (same catalog + same filter) are rejected — the error includes the existing set's ID so the agent can point the user at it. +- Invalid \`catalog_id\` (wrong entity type or not visible to the viewer) is rejected with an actionable error. + +## Examples: +- Create a set of all in-stock Acme items in catalog 123: + - First: \`ads_catalog_search_product\` with \`catalog_id=123\`, \`filter={"and":[{"brand":{"eq":"Acme"}},{"availability":{"eq":"in stock"}}]}\` → confirm sample products + total_count with the user. + - Then: \`ads_catalog_create_product_set\` with \`catalog_id=123\`, \`title="Acme — In Stock"\`, \`filter={"and":[{"brand":{"eq":"Acme"}},{"availability":{"eq":"in stock"}}]}\`. + +## Filter spec: +**Shape:** +- Leaf rule: \`{: {: }}\` +- Compound rule: \`{: [, ...]}\` + +**Logical combinators:** +- \`and\` — match ALL of the nested rules. Example: \`{"and":[{"availability":{"eq":"in stock"}},{"brand":{"eq":"Acme"}}]}\` returns items that are in stock AND Acme-branded. +- \`or\` — match ANY of the nested rules. Example: \`{"or":[{"category":{"contains":"shoe"}},{"category":{"contains":"sneaker"}}]}\` returns items whose category contains either "shoe" or "sneaker". +- \`not\` — invert a single rule. Example: \`{"not":{"brand":{"eq":"Acme"}}}\` returns items whose brand is NOT exactly "Acme". Wraps one rule, not an array. + +**Comparison operators:** +- \`eq\` — exact match. Example: \`{"brand":{"eq":"Instagram"}}\` matches only items with brand exactly "Instagram". +- \`neq\` — does NOT exactly match. Example: \`{"brand":{"neq":"Instagram"}}\` matches items whose brand is anything other than "Instagram". +- \`lt\`, \`lte\` — numeric less-than (strict / inclusive). Example: \`{"priority":{"lt":3}}\` matches items with priority < 3. +- \`gt\`, \`gte\` — numeric greater-than (strict / inclusive). Example: \`{"priority":{"gte":3}}\` matches items with priority >= 3. + +**String operators:** +- \`contains\` — substring match. Example: \`{"category":{"contains":"running shoe"}}\` matches items whose category contains the substring, e.g. "red running shoe", "blue running shoe", "running shoe for kids". +- \`not_contains\` — substring excludes. Example: \`{"category":{"not_contains":"running shoe"}}\` matches items whose category does NOT contain the substring, e.g. "red walking shoe", "sandals", "boots". +- \`starts_with\` — prefix match. Example: \`{"category":{"starts_with":"small"}}\` matches "small sandals", "small t-shirt", etc. **Note:** only valid for the product category field; for other fields use \`contains\`. + +**Set operators:** (right-hand side is an array) +- \`is_any\` — match if value is any one of the listed. Example: \`{"color":{"is_any":["black","blue","brown"]}}\` matches items in any of those colors. +- \`is_not_any\` — match if value is none of the listed. Example: \`{"color":{"is_not_any":["black","blue","brown"]}}\` matches items NOT in any of those colors (e.g. "red", "yellow", "green"). + +**Supported fields (commerce vertical only):** + +- \`age_group\` — string enum, one of: \`adult\`, \`infant\`, \`kids\`, \`newborn\`, \`toddler\`. Use \`eq\` / \`is_any\`. +- \`availability\` — string enum, one of: \`available for order\`, \`in stock\`, \`preorder\`, \`out of stock\`. Use \`eq\` / \`neq\` / \`is_any\` / \`is_not_any\`. +- \`brand\` — string. Brand name from the feed. String operators apply. +- \`category\` — string. Free-form merchant category from the feed (e.g. \`"running shoe"\`). String operators apply (including \`starts_with\`). +- \`color\` — string. String operators apply. +- \`condition\` — string enum, one of: \`new\`, \`refurbished\`, \`used\`. Use \`eq\` / \`neq\` / \`is_any\` / \`is_not_any\`. +- \`currency\` — string ISO-4217 currency code (e.g. \`"USD"\`, \`"GBP"\`). Use \`eq\` / \`is_any\`. +- \`custom_label_0\`, \`custom_label_1\`, \`custom_label_2\`, \`custom_label_3\`, \`custom_label_4\` — string. Free-form merchant labels from the feed. String operators apply. +- \`gender\` — string enum, one of: \`female\`, \`male\`, \`unisex\`. Use \`eq\` / \`is_any\`. +- \`images_fetch_status\` — string. Fetch status of the product's images. Common values: \`fetched\`, \`direct_upload\`, \`fetch_failed\`, \`outdated\`, \`partial_fetch\`, \`not_fetched\`. Use \`eq\` / \`is_any\` (e.g. find products with broken images: \`{"images_fetch_status":{"eq":"fetch_failed"}}\`). Note: the filter field is plural \`images_fetch_status\`, even though the returned response field is singular \`image_fetch_status\`. +- \`material\` — string. String operators apply. +- \`name\` — string. Product name/title from the feed. String operators apply. +- \`pattern\` — string (e.g. \`"striped"\`, \`"polka dot"\`). String operators apply. +- \`price_amount\` — integer; the price multiplied by 100, for all currencies (e.g. \`$4.90 USD\` → \`490\`, \`¥490 JPY\` → \`49000\`). Use numeric operators (\`eq\`, \`lt\`, \`lte\`, \`gt\`, \`gte\`). Note: the field is \`price_amount\`, not \`price\`. +- \`product_expiration_time\` — date/time when the product is no longer available. +- \`product_feed_id\` — integer. The ID for the product feed. Use \`eq\` / \`is_any\`. +- \`product_group_id\` — integer. ID grouping product variants (e.g. all sizes/colors of one shirt share a \`product_group_id\`). Use \`eq\` / \`is_any\`. +- \`product_item_id\` — integer. Meta-assigned numeric product item ID. Use \`eq\` / \`is_any\` for exact lookup. +- \`product_type\` — string. Merchant-defined taxonomy (e.g. \`"Apparel & Accessories > Shoes"\`). String operators apply. +- \`region_id\` — integer. The region ID for the location for a product item. Use \`eq\` / \`is_any\`. +- \`retailer_id\` — string. The merchant-provided unique identifier (SKU). Use \`eq\`, \`is_any\`, etc. for exact matches. +- \`retailer_product_group_id\` — string. The merchant-provided identifier for the product group the item belongs to (the retailer's item group ID). Use \`eq\`, \`is_any\`, etc. for exact matches. +- \`sale_price_amount\` — integer; same format as \`price_amount\` (price × 100). Numeric operators apply. Note: the field is \`sale_price_amount\`, not \`sale_price\`. +- \`size\` — string. String operators apply. +- \`tags\` — string. Tags for product organization. String operators apply. +- \`videos_fetch_status\` — string. The fetch status of associated videos. Use \`eq\` / \`is_any\`. +- \`visibility\` — string enum, one of: \`published\`, \`staging\`, \`hidden\`, \`whitelist_only\`. Items in \`staging\` are not visible to buyers and are not available in dynamic ads. Use \`eq\` / \`is_any\`. + +**Notes:** +- Only the commerce vertical is supported. Field names not in the list above will be rejected. +- Use \`eq\` (not \`contains\`) for enum-typed fields (\`age_group\`, \`availability\`, \`condition\`, \`gender\`, \`visibility\`). +- Value matching is case-insensitive: \`{"brand":{"eq":"acme"}}\` and \`{"brand":{"eq":"Acme"}}\` return the same items. Do not bother trying multiple casings of the same value to "broaden" matches. +- \`price_amount\` and \`sale_price_amount\` are integers — the price multiplied by 100. \`{"price_amount":{"lt":"5000"}}\` means "less than $50.00", not "less than $5000". Bare \`price\` / \`sale_price\` are not valid filter fields for product items (they apply to other verticals only). + +**Common wrong field name aliases:** +- \`title\`, \`product_name\` → use \`name\` (the product title/name field). +- \`price\`, \`sale_price\`, \`current_price\` → use \`price_amount\` / \`sale_price_amount\` (integers, price × 100). +- \`item_group_id\` → use \`product_group_id\`. +- \`product_id\` → use \`product_item_id\` (Meta-assigned numeric ID). +- \`image_fetch_status\` (singular) → use \`images_fetch_status\` (plural). +- \`link\`, \`description\`, \`sku\` → not supported filter fields. Use \`retailer_id\` for SKU lookups.`, + params: [ + { + name: 'catalog_id', + type: 'string', + required: true, + description: `The product catalog ID to create the set under.`, + }, + { + name: 'filter', + type: 'string', + required: true, + description: `JSON-encoded rule that selects which products from the catalog belong to this set. Shape: a leaf rule is \`{"":{"":}}\` (e.g. \`{"availability":{"eq":"in stock"}}\`); combine leaves with \`{"and":[...]}\` / \`{"or":[...]}\` / \`{"not":{...}}\` (e.g. \`{"and":[{"brand":{"eq":"Acme"}},{"availability":{"eq":"in stock"}}]}\`). See the **Filter spec** section in this tool's description for the full operator and field catalog. Validated against the catalog's vertical and item type before creation; invalid filters are rejected with an actionable error. Strongly recommend calling \`ads_catalog_search_product\` with the same \`catalog_id\` + \`filter\` first to preview matched products and confirm with the user before committing.`, + }, + { + name: 'title', + type: 'string', + required: true, + description: `REQUIRED. Human-readable display name for the new product set (e.g. "Summer Sale — In Stock"). Must be a non-empty string after trimming whitespace — always set it, even when the user only describes the filter.`, + }, + { + name: 'advertiser_request', + type: 'string', + required: false, + description: `Capture what the advertiser is actually asking for, in their exact words, quoted from their own messages word for word wherever you can. A question, lookup, or check counts as a real request (for example 'do I have X listed?', 'show me my Y', 'how many Z are left?'), so capture it. Pull their request from anywhere in the conversation, including across multiple turns: if they state a goal or problem early and then approve or narrow an action later (for example a brief 'yes, go ahead'), combine these into one request that keeps the action and its original subject and conditions, and capture that earlier request, not the bare confirmation. If they ask for several things, include every part, not just the one this tool handles. Stay strictly in the advertiser's own vocabulary: do not paraphrase, summarize, or shift their words into a more formal register; do not upgrade their plain words into domain or industry terms; do not phrase the request as the command or operation this tool performs; and do not add metric abbreviations or technical, product, or system field names they did not say themselves. This holds in every language: keep their phrasing in the language they used and never substitute the technical equivalent, translated or not, unless the advertiser used that term themselves. Leave this empty only when the message is pure greeting, small talk, thanks, or acknowledgment with no request of any kind; do not invent or infer a request that was not expressed. Do not include names, contact details, or other personal information.`, + }, + { + name: 'retailer_id', + type: 'string', + required: false, + description: `Optional merchant-assigned identifier (SKU-like) for the product set. Only set when the user explicitly provides one.`, + }, + ], + }, + { + name: 'facebookadsmcp_ads_catalog_delete_product', + description: `Delete a product item from a catalog. The item is removed from the catalog and immediately stops appearing in ads and on Meta surfaces. + +When to use: +- A merchant wants to remove a product item from their catalog. +- You already know the product item ID (from ads_catalog_search_product or ads_catalog_get_product_details). +- The item should no longer appear in ads or on any Meta surface. + +When NOT to use: +- To hide a product without permanently removing it — use ads_catalog_update_product with visibility="hidden" instead. Hidden products stop appearing to buyers and in dynamic ads but can be made visible again by setting visibility="published". Prefer this whenever the merchant might want the product back. +- To mark a product as out of stock — use ads_catalog_update_product with availability="out of stock" instead. +- To delete a product set — use ads_catalog_product_set_delete. +- To delete an entire catalog — not supported via MCP; the advertiser must use Commerce Manager. + +Notes: +- This action cannot be undone through the API. Treat it as permanent: a deleted product cannot be restored, and recreating it requires re-supplying the product's details. If you only need to stop a product from showing, hide it via ads_catalog_update_product (visibility="hidden") rather than deleting it. +- Mirrors the Graph API endpoint DELETE /{product_item_id}.`, + params: [ + { + name: 'product_id', + type: 'string', + required: true, + description: `The ID of the product item to delete (numeric string).`, + }, + { + name: 'advertiser_request', + type: 'string', + required: false, + description: `Capture what the advertiser is actually asking for, in their exact words, quoted from their own messages word for word wherever you can. A question, lookup, or check counts as a real request (for example 'do I have X listed?', 'show me my Y', 'how many Z are left?'), so capture it. Pull their request from anywhere in the conversation, including across multiple turns: if they state a goal or problem early and then approve or narrow an action later (for example a brief 'yes, go ahead'), combine these into one request that keeps the action and its original subject and conditions, and capture that earlier request, not the bare confirmation. If they ask for several things, include every part, not just the one this tool handles. Stay strictly in the advertiser's own vocabulary: do not paraphrase, summarize, or shift their words into a more formal register; do not upgrade their plain words into domain or industry terms; do not phrase the request as the command or operation this tool performs; and do not add metric abbreviations or technical, product, or system field names they did not say themselves. This holds in every language: keep their phrasing in the language they used and never substitute the technical equivalent, translated or not, unless the advertiser used that term themselves. Leave this empty only when the message is pure greeting, small talk, thanks, or acknowledgment with no request of any kind; do not invent or infer a request that was not expressed. Do not include names, contact details, or other personal information.`, + }, + ], + }, + { + name: 'facebookadsmcp_ads_catalog_event_source_connect', + description: `Connect an event source to a product catalog so its signals can be matched against the catalog's products (for dynamic ads and Advantage+ catalog ads). Event sources are also commonly called "pixels"; the source can be a PIXEL (data from the seller's website), an APP (data from the seller's app), or an OFFLINE_CONVERSION_DATA_SET (data from in-store transactions). The Conversions API (CAPI) is an enhancement that augments PIXEL or APP data — it is not a standalone event source type. + +When to use: +- The advertiser wants to connect a pixel / event source to a catalog (e.g. after ads_catalog_event_source_get shows an expected source is missing). +- To fix product-matching gaps where a catalog is not receiving a source's events. + +When NOT to use: +- To list the event sources already connected to a catalog (use ads_catalog_event_source_get). +- To check event-source match rate or setup issues (use ads_catalog_event_source_get_health). +- To find which catalogs an event source is connected to (use ads_catalog_event_source_get_catalogs). + +Notes: +- The operation is idempotent: connecting an already-connected source succeeds and returns already_connected=true without making a change. +- The catalog and event source must belong to the same business.`, + params: [ + { + name: 'catalog_id', + type: 'string', + required: true, + description: `The product catalog ID to connect the event source to (numeric string).`, + }, + { + name: 'event_source_id', + type: 'string', + required: true, + description: `The event source ID to connect: a pixel, Conversions API (CAPI) app, or offline conversion data set ID (numeric string).`, + }, + { + name: 'advertiser_request', + type: 'string', + required: false, + description: `Capture what the advertiser is actually asking for, in their exact words, quoted from their own messages word for word wherever you can. A question, lookup, or check counts as a real request (for example 'do I have X listed?', 'show me my Y', 'how many Z are left?'), so capture it. Pull their request from anywhere in the conversation, including across multiple turns: if they state a goal or problem early and then approve or narrow an action later (for example a brief 'yes, go ahead'), combine these into one request that keeps the action and its original subject and conditions, and capture that earlier request, not the bare confirmation. If they ask for several things, include every part, not just the one this tool handles. Stay strictly in the advertiser's own vocabulary: do not paraphrase, summarize, or shift their words into a more formal register; do not upgrade their plain words into domain or industry terms; do not phrase the request as the command or operation this tool performs; and do not add metric abbreviations or technical, product, or system field names they did not say themselves. This holds in every language: keep their phrasing in the language they used and never substitute the technical equivalent, translated or not, unless the advertiser used that term themselves. Leave this empty only when the message is pure greeting, small talk, thanks, or acknowledgment with no request of any kind; do not invent or infer a request that was not expressed. Do not include names, contact details, or other personal information.`, + }, + ], + }, + { + name: 'facebookadsmcp_ads_catalog_event_source_disconnect', + description: `Disconnect an event source from a product catalog, removing the link so the source's signals are no longer matched against the catalog's products. Event sources are also commonly called "pixels"; the source can be a PIXEL (data from the seller's website), an APP (data from the seller's app), or an OFFLINE_CONVERSION_DATA_SET (data from in-store transactions). The Conversions API (CAPI) is an enhancement that augments PIXEL or APP data — it is not a standalone event source type. + +When to use: +- The advertiser wants to disconnect / unlink / remove a pixel or event source from a catalog. +- To stop a source's events from being matched against a catalog's products. + +When NOT to use: +- To connect an event source to a catalog (use ads_catalog_event_source_connect). +- To list the event sources connected to a catalog (use ads_catalog_event_source_get). +- To check event-source match rate or setup issues (use ads_catalog_event_source_get_health). + +Notes: +- The operation is idempotent: disconnecting a source that is not connected succeeds and returns was_connected=false without making a change. +- This is a destructive change that can reduce product matching for dynamic / Advantage+ catalog ads; it is blocked when the catalog has active ad spend (Agent Controls). +- The catalog and event source must belong to the same business.`, + params: [ + { + name: 'catalog_id', + type: 'string', + required: true, + description: `The product catalog ID to disconnect the event source from (numeric string).`, + }, + { + name: 'event_source_id', + type: 'string', + required: true, + description: `The event source ID to disconnect: a pixel, Conversions API (CAPI) app, or offline conversion data set ID (numeric string).`, + }, + { + name: 'advertiser_request', + type: 'string', + required: false, + description: `Capture what the advertiser is actually asking for, in their exact words, quoted from their own messages word for word wherever you can. A question, lookup, or check counts as a real request (for example 'do I have X listed?', 'show me my Y', 'how many Z are left?'), so capture it. Pull their request from anywhere in the conversation, including across multiple turns: if they state a goal or problem early and then approve or narrow an action later (for example a brief 'yes, go ahead'), combine these into one request that keeps the action and its original subject and conditions, and capture that earlier request, not the bare confirmation. If they ask for several things, include every part, not just the one this tool handles. Stay strictly in the advertiser's own vocabulary: do not paraphrase, summarize, or shift their words into a more formal register; do not upgrade their plain words into domain or industry terms; do not phrase the request as the command or operation this tool performs; and do not add metric abbreviations or technical, product, or system field names they did not say themselves. This holds in every language: keep their phrasing in the language they used and never substitute the technical equivalent, translated or not, unless the advertiser used that term themselves. Leave this empty only when the message is pure greeting, small talk, thanks, or acknowledgment with no request of any kind; do not invent or infer a request that was not expressed. Do not include names, contact details, or other personal information.`, + }, + ], + }, + { + name: 'facebookadsmcp_ads_catalog_event_source_get', + description: `List the event sources connected to a product catalog. Event sources are also commonly called "pixels"; this tool returns ALL connected source types and labels each with its source_type: PIXEL (data from the seller's website), APP (data from the seller's app), or OFFLINE_CONVERSION_DATA_SET (data from in-store transactions). The Conversions API (CAPI) is an enhancement that augments PIXEL or APP data — it is not a standalone event source type. Treat a user request about the "pixels connected to the catalog" as a request for this tool unless they explicitly restrict to a specific source type. + +When to use: +- To see which pixels / event sources are connected to a catalog +- As a first step before checking event-source match rate or setup issues (then use ads_catalog_event_source_get_health) + +When NOT to use: +- To get match rate or setup issues for an event source (use ads_catalog_event_source_get_health) +- To check feed-level catalog health and diagnostics (use ads_catalog_get_diagnostics)`, + params: [ + { + name: 'catalog_id', + type: 'string', + required: true, + description: `The product catalog ID (numeric string).`, + }, + { + name: 'advertiser_request', + type: 'string', + required: false, + description: `Capture what the advertiser is actually asking for, in their exact words, quoted from their own messages word for word wherever you can. A question, lookup, or check counts as a real request (for example 'do I have X listed?', 'show me my Y', 'how many Z are left?'), so capture it. Pull their request from anywhere in the conversation, including across multiple turns: if they state a goal or problem early and then approve or narrow an action later (for example a brief 'yes, go ahead'), combine these into one request that keeps the action and its original subject and conditions, and capture that earlier request, not the bare confirmation. If they ask for several things, include every part, not just the one this tool handles. Stay strictly in the advertiser's own vocabulary: do not paraphrase, summarize, or shift their words into a more formal register; do not upgrade their plain words into domain or industry terms; do not phrase the request as the command or operation this tool performs; and do not add metric abbreviations or technical, product, or system field names they did not say themselves. This holds in every language: keep their phrasing in the language they used and never substitute the technical equivalent, translated or not, unless the advertiser used that term themselves. Leave this empty only when the message is pure greeting, small talk, thanks, or acknowledgment with no request of any kind; do not invent or infer a request that was not expressed. Do not include names, contact details, or other personal information.`, + }, + { + name: 'limit', + type: 'integer', + required: false, + description: `Maximum number of event sources to return (default: 20, max: 100).`, + }, + ], + }, + { + name: 'facebookadsmcp_ads_catalog_event_source_get_catalogs', + description: `Get the product catalogs connected to a given event source (pixel, CAPI app, or offline conversion data set). + +## When to use: +- Step 1 when answering "how will this pixel event be matched?" — call this tool with the pixel/event source ID, then call ads_catalog_search_product on each returned catalog_id with filter={"retailer_id":{"eq":""}} (the pixel content ID) to find which products would be matched. +- When the advertiser wants to know which of their product catalogs are linked to a specific pixel or event source. + +## When NOT to use: +- To search for a specific product directly — use ads_catalog_search_product with a known catalog_id. +- When you already know the catalog ID — skip this tool and call ads_catalog_search_product directly. Note that if the catalog is not connected to the event source, the event won't be matched against the catalog unless the catalog ID is specified in the event + +## Pixel-matching workflow: +1. Call ads_catalog_event_source_get_catalogs with the pixel/event source ID. +2. For each returned catalog_id, call ads_catalog_search_product with filter={"retailer_id":{"eq":""}} where is the content ID from the pixel event. +3. Products found in step 2 are the ones that would be matched by that pixel event. + +## Errors: +- If the event source does not exist or the viewer cannot access it, the tool returns an \`Invalid Params\` error.`, + params: [ + { + name: 'event_source_id', + type: 'string', + required: true, + description: `The pixel or event source ID (numeric string) whose connected catalogs to list.`, + }, + { + name: 'advertiser_request', + type: 'string', + required: false, + description: `Capture what the advertiser is actually asking for, in their exact words, quoted from their own messages word for word wherever you can. A question, lookup, or check counts as a real request (for example 'do I have X listed?', 'show me my Y', 'how many Z are left?'), so capture it. Pull their request from anywhere in the conversation, including across multiple turns: if they state a goal or problem early and then approve or narrow an action later (for example a brief 'yes, go ahead'), combine these into one request that keeps the action and its original subject and conditions, and capture that earlier request, not the bare confirmation. If they ask for several things, include every part, not just the one this tool handles. Stay strictly in the advertiser's own vocabulary: do not paraphrase, summarize, or shift their words into a more formal register; do not upgrade their plain words into domain or industry terms; do not phrase the request as the command or operation this tool performs; and do not add metric abbreviations or technical, product, or system field names they did not say themselves. This holds in every language: keep their phrasing in the language they used and never substitute the technical equivalent, translated or not, unless the advertiser used that term themselves. Leave this empty only when the message is pure greeting, small talk, thanks, or acknowledgment with no request of any kind; do not invent or infer a request that was not expressed. Do not include names, contact details, or other personal information.`, + }, + ], + }, + { + name: 'facebookadsmcp_ads_catalog_event_source_get_health', + description: `Report the match rate and setup issues for the event sources connected to a product catalog. "Match rate" is the percentage of conversion events (e.g. Purchase, AddToCart) whose content IDs matched a product in the catalog — higher match rates mean better ad targeting and measurement. + +Each event source has a source_type: PIXEL (data from the seller's website), APP (data from the seller's app), or OFFLINE_CONVERSION_DATA_SET (data from in-store transactions); the Conversions API (CAPI) is an enhancement that augments PIXEL or APP data, not a standalone source type. + +This tool returns the latest overall match rate (match_rate) AND the historical per-date, per-event-type match rate stats over the past 28 days (match_rate_stats). The historical data is useful to detect trends, regressions, or sudden changes in match quality over time. It also returns a list of setup issues (each with a type, description, and severity). This is the same measurement-layer data Commerce Manager surfaces in its "Events" tab. + +When to use: +- To diagnose why catalog products are not matching signals / events ("low match rate", "events not matching products") +- To detect match rate trends or regressions over the past month +- To list the setup issues for a pixel / event source connected to a catalog +- To report match quality for one event source (pass event_source_id) or all connected sources (omit it) + +When NOT to use: +- To just list which event sources are connected, without health data (use ads_catalog_event_source_get) +- To check feed-level catalog diagnostics like broken images or missing fields (use ads_catalog_get_diagnostics) +- To run Dynamic Ads integration health checks (use ads_catalog_get_dynamic_ads_health)`, + params: [ + { + name: 'catalog_id', + type: 'string', + required: true, + description: `The product catalog ID (numeric string).`, + }, + { + name: 'advertiser_request', + type: 'string', + required: false, + description: `Capture what the advertiser is actually asking for, in their exact words, quoted from their own messages word for word wherever you can. A question, lookup, or check counts as a real request (for example 'do I have X listed?', 'show me my Y', 'how many Z are left?'), so capture it. Pull their request from anywhere in the conversation, including across multiple turns: if they state a goal or problem early and then approve or narrow an action later (for example a brief 'yes, go ahead'), combine these into one request that keeps the action and its original subject and conditions, and capture that earlier request, not the bare confirmation. If they ask for several things, include every part, not just the one this tool handles. Stay strictly in the advertiser's own vocabulary: do not paraphrase, summarize, or shift their words into a more formal register; do not upgrade their plain words into domain or industry terms; do not phrase the request as the command or operation this tool performs; and do not add metric abbreviations or technical, product, or system field names they did not say themselves. This holds in every language: keep their phrasing in the language they used and never substitute the technical equivalent, translated or not, unless the advertiser used that term themselves. Leave this empty only when the message is pure greeting, small talk, thanks, or acknowledgment with no request of any kind; do not invent or infer a request that was not expressed. Do not include names, contact details, or other personal information.`, + }, + { + name: 'event_source_id', + type: 'string', + required: false, + description: `Optional event source ID (pixel / app / offline data set) to scope the report to a single connected source. Omit to report on all event sources connected to the catalog.`, + }, + { + name: 'limit', + type: 'integer', + required: false, + description: `Maximum number of event sources to report on when event_source_id is omitted (default: 20, max: 100).`, + }, + ], + }, + { + name: 'facebookadsmcp_ads_catalog_event_source_get_recommendations', + description: `Recommend the event sources (pixels) to connect to a product catalog. Meta computes, for catalogs with weak or missing signal coverage, the event sources whose conversion events best match the catalog's products. This tool returns those recommendations for a catalog: recommended_pixel_id is the single best pixel to connect, and event_sources lists the recommended sources. + +Each event source has a source_type: PIXEL (data from the seller's website), APP (data from the seller's app), or OFFLINE_CONVERSION_DATA_SET (data from in-store transactions); the Conversions API (CAPI) is an enhancement that augments PIXEL or APP data, not a standalone source type. + +When to use: +- To answer "which pixel / event source should I connect to this catalog?" +- During catalog setup, to proactively suggest the best signal source to connect + +When NOT to use: +- To list the event sources ALREADY connected to a catalog (use ads_catalog_event_source_get) +- To get match rate / setup issues for an already-connected source (use ads_catalog_event_source_get_health) +- To check feed-level catalog diagnostics like broken images or missing fields (use ads_catalog_get_diagnostics)`, + params: [ + { + name: 'catalog_id', + type: 'string', + required: true, + description: `The product catalog ID (numeric string).`, + }, + { + name: 'advertiser_request', + type: 'string', + required: false, + description: `Capture what the advertiser is actually asking for, in their exact words, quoted from their own messages word for word wherever you can. A question, lookup, or check counts as a real request (for example 'do I have X listed?', 'show me my Y', 'how many Z are left?'), so capture it. Pull their request from anywhere in the conversation, including across multiple turns: if they state a goal or problem early and then approve or narrow an action later (for example a brief 'yes, go ahead'), combine these into one request that keeps the action and its original subject and conditions, and capture that earlier request, not the bare confirmation. If they ask for several things, include every part, not just the one this tool handles. Stay strictly in the advertiser's own vocabulary: do not paraphrase, summarize, or shift their words into a more formal register; do not upgrade their plain words into domain or industry terms; do not phrase the request as the command or operation this tool performs; and do not add metric abbreviations or technical, product, or system field names they did not say themselves. This holds in every language: keep their phrasing in the language they used and never substitute the technical equivalent, translated or not, unless the advertiser used that term themselves. Leave this empty only when the message is pure greeting, small talk, thanks, or acknowledgment with no request of any kind; do not invent or infer a request that was not expressed. Do not include names, contact details, or other personal information.`, + }, + ], + }, + { + name: 'facebookadsmcp_ads_catalog_get_catalogs', + description: `Gets the catalogs associated with the authenticated user (up to 100). + +## When to use: +- Call this tool when the user asks to see their catalogs or list available catalogs. +- Use when the user wants to browse or find a catalog before drilling into its products or product sets. + +## When NOT to use: +- Do NOT call this tool for fetching details about a specific catalog (use ads_catalog_get_details for catalog details, or ads_catalog_get_product_sets to browse a catalog's product sets). +- Do NOT call this tool for fetching individual product items (use ads_catalog_get_product_details instead). + +## Input Parameters: +All parameters are optional; omit them all to list every catalog for the authenticated user/token. Catalogs belong to a Business, not an ad account. To scope to a business, pass business_id. If you only have an ad account, pass ad_account_id and it will be resolved to its owning business (business_id takes precedence if both are provided). + +## Response Guidelines: +1. Present the list of catalogs including their ID, name, and vertical. +2. If no catalogs are found, inform the user that they may not have any catalogs associated with their account.`, + params: [ + { + name: 'ad_account_id', + type: 'string', + required: false, + description: `Filter catalogs by ad account ID. The ad account is resolved to its owning business and only catalogs owned by that business are returned. Ignored if business_id is also provided. Use this when you have an ad account but not its business ID.`, + }, + { + name: 'advertiser_request', + type: 'string', + required: false, + description: `Capture what the advertiser is actually asking for, in their exact words, quoted from their own messages word for word wherever you can. A question, lookup, or check counts as a real request (for example 'do I have X listed?', 'show me my Y', 'how many Z are left?'), so capture it. Pull their request from anywhere in the conversation, including across multiple turns: if they state a goal or problem early and then approve or narrow an action later (for example a brief 'yes, go ahead'), combine these into one request that keeps the action and its original subject and conditions, and capture that earlier request, not the bare confirmation. If they ask for several things, include every part, not just the one this tool handles. Stay strictly in the advertiser's own vocabulary: do not paraphrase, summarize, or shift their words into a more formal register; do not upgrade their plain words into domain or industry terms; do not phrase the request as the command or operation this tool performs; and do not add metric abbreviations or technical, product, or system field names they did not say themselves. This holds in every language: keep their phrasing in the language they used and never substitute the technical equivalent, translated or not, unless the advertiser used that term themselves. Leave this empty only when the message is pure greeting, small talk, thanks, or acknowledgment with no request of any kind; do not invent or infer a request that was not expressed. Do not include names, contact details, or other personal information.`, + }, + { + name: 'business_id', + type: 'string', + required: false, + description: `Filter catalogs by business ID. Only returns catalogs owned by this business.`, + }, + { + name: 'cursor', + type: 'string', + required: false, + description: `Pagination cursor for fetching the next page. Set this to the EXACT value of \`page_info.after_cursor\` from the previous response — copy the raw string as-is. Do NOT invent a cursor or use placeholder text. Omit entirely to start from page 1.`, + }, + { + name: 'limit', + type: 'integer', + required: false, + description: `Maximum number of catalogs to return (default: 20, max: 100).`, + }, + { + name: 'name', + type: 'string', + required: false, + description: `Filter catalogs by name (case-insensitive, diacritic-insensitive substring match).`, + }, + ], + }, + { + name: 'facebookadsmcp_ads_catalog_get_data_sources', + description: `List ALL data sources connected to a catalog — not just product feeds, but also Batch API, Graph API, partner integrations (Shopify, WooCommerce, SFCC), smart pixel, website crawling, and FB/IG post sourcing. Each data source reports its type, name, the number of products it has ingested, when it last updated, and its latest update status. This matches what the advertiser sees on the "Data Sources" tab in Commerce Manager. + +## When to use: +- The user asks what data sources, feeds, or integrations are connected to their catalog ("what are my data sources?", "is my Shopify connected?", "how are my products coming in?"). +- The user asks about the status of their products or catalog and you need to know every way data flows into it before answering. +- The user wants a complete inventory of ingestion methods, not just file/URL feeds. + +## When NOT to use: +- The user wants the upload history of one specific feed — use \`ads_catalog_get_product_feed_upload_sessions\`. +- The user wants the configuration (schedule, URL) of one specific feed — use \`ads_catalog_get_product_feed_details\`. +- The user wants to inspect products directly — use \`ads_catalog_search_product\`. +- The user wants overall catalog metadata (name, vertical, counts) — use \`ads_catalog_get_details\`. + +## Input Requirements: +- \`catalog_id\` (required): The product catalog ID. +- \`limit\` (optional): Maximum number of data sources to return. +- \`cursor\` (optional): Pagination cursor from a previous response. + +## Output Format: +JSON with: +- \`catalog_id\` — echo of the input catalog ID. +- \`data_sources\` — list of data sources (most recent first) with id, name, type, product_count, last_update_time, and status. +- \`total_count\` — total number of data sources connected to the catalog, across all pages. +- \`has_more\` — whether more data sources exist beyond this page. +- \`next_cursor\` — cursor to fetch the next page, or null. + +## Errors: +- If the catalog does not exist or the viewer cannot see it, the tool returns an \`Invalid Params\` error.`, + params: [ + { + name: 'catalog_id', + type: 'string', + required: true, + description: `The product catalog ID (numeric string) whose data sources to list.`, + }, + { + name: 'advertiser_request', + type: 'string', + required: false, + description: `Capture what the advertiser is actually asking for, in their exact words, quoted from their own messages word for word wherever you can. A question, lookup, or check counts as a real request (for example 'do I have X listed?', 'show me my Y', 'how many Z are left?'), so capture it. Pull their request from anywhere in the conversation, including across multiple turns: if they state a goal or problem early and then approve or narrow an action later (for example a brief 'yes, go ahead'), combine these into one request that keeps the action and its original subject and conditions, and capture that earlier request, not the bare confirmation. If they ask for several things, include every part, not just the one this tool handles. Stay strictly in the advertiser's own vocabulary: do not paraphrase, summarize, or shift their words into a more formal register; do not upgrade their plain words into domain or industry terms; do not phrase the request as the command or operation this tool performs; and do not add metric abbreviations or technical, product, or system field names they did not say themselves. This holds in every language: keep their phrasing in the language they used and never substitute the technical equivalent, translated or not, unless the advertiser used that term themselves. Leave this empty only when the message is pure greeting, small talk, thanks, or acknowledgment with no request of any kind; do not invent or infer a request that was not expressed. Do not include names, contact details, or other personal information.`, + }, + { + name: 'cursor', + type: 'string', + required: false, + description: `Pagination cursor from a previous response to fetch the next page of data sources. Set this to the EXACT value of next_cursor from the previous response. Omit to start from the most recent source.`, + }, + { + name: 'limit', + type: 'integer', + required: false, + description: `Maximum number of data sources to return, most recent first. Defaults to 20, capped at 100.`, + }, + ], + }, + { + name: 'facebookadsmcp_ads_catalog_get_details', + description: `Get catalog details including name, vertical, product/product set counts, business info, and optionally a paginated list of feeds. + +When to use: +- To inspect a catalog's metadata and configuration +- To list feeds associated with a catalog (optionally filtered by feed_ingestion_source_type or override_type) +- As a first step before drilling into products or diagnostics + +When NOT to use: +- To list products in a catalog (use ads_catalog_search_product) +- To check catalog health (use ads_catalog_get_diagnostics) +- To list catalogs for a business (use ads_catalog_get_catalogs) +- To list ALL of a catalog's data sources (the \`feeds\` field here covers file/URL product feeds only; use ads_catalog_get_data_sources for Batch API, partner integrations like Shopify/WooCommerce, smart pixel, website crawling, and Graph API) + +Response Guidelines: +- If \`product_sets_with_items_blocked_in_ads\` is non-empty, the listed actively-advertised product sets have items blocked from appearing, with a per-set count. Call this out to the user (cite per-set counts; do not sum them into a catalog-wide total, since a product can belong to multiple sets) and follow up with ads_catalog_get_diagnostics (using this catalog_id) to surface and fix the specific issues.`, + params: [ + { + name: 'catalog_id', + type: 'string', + required: true, + description: `The product catalog ID (numeric string).`, + }, + { + name: 'advertiser_request', + type: 'string', + required: false, + description: `Capture what the advertiser is actually asking for, in their exact words, quoted from their own messages word for word wherever you can. A question, lookup, or check counts as a real request (for example 'do I have X listed?', 'show me my Y', 'how many Z are left?'), so capture it. Pull their request from anywhere in the conversation, including across multiple turns: if they state a goal or problem early and then approve or narrow an action later (for example a brief 'yes, go ahead'), combine these into one request that keeps the action and its original subject and conditions, and capture that earlier request, not the bare confirmation. If they ask for several things, include every part, not just the one this tool handles. Stay strictly in the advertiser's own vocabulary: do not paraphrase, summarize, or shift their words into a more formal register; do not upgrade their plain words into domain or industry terms; do not phrase the request as the command or operation this tool performs; and do not add metric abbreviations or technical, product, or system field names they did not say themselves. This holds in every language: keep their phrasing in the language they used and never substitute the technical equivalent, translated or not, unless the advertiser used that term themselves. Leave this empty only when the message is pure greeting, small talk, thanks, or acknowledgment with no request of any kind; do not invent or infer a request that was not expressed. Do not include names, contact details, or other personal information.`, + }, + { + name: 'feed_cursor', + type: 'string', + required: false, + description: `Pagination cursor from a previous response to fetch the next page of feeds.`, + }, + { + name: 'feed_ingestion_source_type', + type: 'string', + required: false, + description: `Optional filter to return only feeds of this role: primary_feed (owns items) or supplementary_feed (only enriches items owned by a primary feed). Omit to return feeds of all roles.`, + }, + { + name: 'feed_limit', + type: 'integer', + required: false, + description: `Number of feeds to return. Omit to exclude feeds from the response. Defaults to 25 when feed_cursor is provided without feed_limit. If explicitly set to 0, feeds are always excluded.`, + }, + { + name: 'override_type', + type: 'string', + required: false, + description: `Optional filter to return only override (localization) feeds of this type: language, country, version, catalog_segment_customize_default, language_and_country, batch_api_language_or_country, smart_pixel_language_or_country, local. Omit to return feeds regardless of override type.`, + }, + ], + }, + { + name: 'facebookadsmcp_ads_catalog_get_diagnostics', + description: `Fetches diagnostic issues for a product catalog, including errors and warnings that may affect ad delivery. + +## When to use: +- Call this tool when the user reports issues with their catalog or products not showing in ads. +- Use when the user asks about catalog errors, warnings, or quality issues (including broken images, missing fields, policy violations). +- Use to diagnose why products may not be eligible for certain ad surfaces. +- Use when the user asks about broken or failed product images at the catalog level. Note: for per-product image status, also check the \`image_fetch_status\` field from \`ads_catalog_search_product\`. + +## When NOT to use: +- Do NOT call this tool for individual product details — use ads_catalog_search_product instead. +- Do NOT call this tool for feed rules, feed transformations, or why product data looks different from the feed file — use ads_catalog_get_feed_rules instead. + +## Response Guidelines: +1. Prioritize MUST_FIX severity issues over OPPORTUNITY (should_fix) issues. +2. For each diagnostic, explain what it means and suggest corrective actions. +3. Group related diagnostics if multiple exist. +4. number_of_affected_items counts affected VARIANTS/SKUs, not deduplicated products. Always describe it to the advertiser as the number of affected items/variants (e.g. "34 affected items"). Do NOT present it as a count of "products", do NOT build a products-vs-variants comparison from it, and do NOT infer how many distinct products are affected or whether products have single/multiple variants — that grouped product count is not available from this tool. +5. When affected_channels is non-empty, clearly state the issue only affects those specific channels — do NOT say it blocks all ad delivery. An empty affected_channels list means the issue applies broadly. Channel name mapping: "mini_shops" = Facebook/Instagram Shops, "da" = Dynamic Ads, "marketplace" = Facebook Marketplace, "ig_shopping" = Instagram Shopping, "whatsapp" = WhatsApp catalog. +6. Do NOT overstate severity: not all MUST_FIX issues block all ads. Some only affect specific surfaces (e.g., mini_shops). Report the scope accurately based on affected_channels. +7. Note that affected item counts may overlap across issues — a single product can have multiple issues. Do NOT sum counts to estimate total affected products. +8. To see which specific products are affected by an issue, you can try \`ads_catalog_search_product\` with its \`error_type\` parameter. Not every diagnostic type is filterable, so treat this as best-effort: if the filter returns no products, fall back to reporting the issue and its affected-item count rather than claiming there are none.`, + params: [ + { + name: 'catalog_id', + type: 'string', + required: true, + description: `The product catalog ID (numeric string).`, + }, + { + name: 'advertiser_request', + type: 'string', + required: false, + description: `Capture what the advertiser is actually asking for, in their exact words, quoted from their own messages word for word wherever you can. A question, lookup, or check counts as a real request (for example 'do I have X listed?', 'show me my Y', 'how many Z are left?'), so capture it. Pull their request from anywhere in the conversation, including across multiple turns: if they state a goal or problem early and then approve or narrow an action later (for example a brief 'yes, go ahead'), combine these into one request that keeps the action and its original subject and conditions, and capture that earlier request, not the bare confirmation. If they ask for several things, include every part, not just the one this tool handles. Stay strictly in the advertiser's own vocabulary: do not paraphrase, summarize, or shift their words into a more formal register; do not upgrade their plain words into domain or industry terms; do not phrase the request as the command or operation this tool performs; and do not add metric abbreviations or technical, product, or system field names they did not say themselves. This holds in every language: keep their phrasing in the language they used and never substitute the technical equivalent, translated or not, unless the advertiser used that term themselves. Leave this empty only when the message is pure greeting, small talk, thanks, or acknowledgment with no request of any kind; do not invent or infer a request that was not expressed. Do not include names, contact details, or other personal information.`, + }, + { + name: 'limit', + type: 'integer', + required: false, + description: `Maximum number of diagnostics to return (default: 20, max: 100).`, + }, + { + name: 'severity', + type: 'string', + required: false, + description: `Filter diagnostics by severity (case-sensitive): must_fix, opportunity, or should_fix. If omitted, returns all.`, + }, + ], + }, + { + name: 'facebookadsmcp_ads_catalog_get_dynamic_ads_health', + description: `Run Dynamic Ads (DA) integration health checks for either a product catalog OR a single product set. Each check returns pass/fail. + +## Scope (catalog vs product set): +- Provide \`catalog_id\` to run catalog-level checks: pixel/event source setup, match rate, eligible product count, and common DA configuration issues. +- Provide \`product_set_id\` to run product-set-level checks: these are creative-quality checks (e.g. slideshow/video quality) for the set, NOT the catalog-wide DA-setup family above. The owning catalog is resolved automatically — you do not need to pass catalog_id as well. +- At least one of \`catalog_id\` / \`product_set_id\` is required. If both are given, \`product_set_id\` takes precedence and \`catalog_id\` is ignored. +- The response echoes \`scope\` ("catalog" or "product_set") so you can confirm which checks ran. + +This tool is different from ads_catalog_get_diagnostics: that tool reports item-level product data quality issues (missing images, feed errors, policy violations with affected-product counts). This tool checks DA / creative-quality health rather than per-item data quality. + +## When to use: +- When the user asks about Dynamic Ads setup health or readiness for a catalog (pass catalog_id). +- When the user reports that their Dynamic Ads are not delivering or have poor performance. +- When the user asks about pixel setup, event source configuration, or match rate issues. +- When the user wants to check the creative quality of a specific product set for Dynamic Ads (pass product_set_id). +- When the user asks "why are my Dynamic Ads not working?" or "is my catalog set up for DA?" + +## When NOT to use: +- Do NOT use for item-level catalog diagnostics (feed errors, policy violations, broken images, affected product counts) — use ads_catalog_get_diagnostics instead. +- Do NOT use for individual product details — use ads_catalog_get_product_details instead. +- Do NOT use for feed rules or feed transformations — use ads_catalog_get_feed_rules instead. +- Do NOT use for product set membership — use ads_catalog_get_product_set_products instead. + +## Response Guidelines: +1. Prioritize failed checks — these indicate issues blocking or degrading DA performance. +2. For each failed check, explain what the issue means and suggest corrective actions. +3. Group related checks if multiple exist (e.g. multiple pixel-related failures). +4. The severity field indicates urgency: MUST_FIX issues should be addressed first. +5. If all checks pass, confirm that the catalog or product set is healthy. +6. Use the action_uri when available to point the user to the relevant Commerce Manager page. +7. If the user also has item-level product issues, suggest running ads_catalog_get_diagnostics separately.`, + params: [ + { + name: 'advertiser_request', + type: 'string', + required: false, + description: `Capture what the advertiser is actually asking for, in their exact words, quoted from their own messages word for word wherever you can. A question, lookup, or check counts as a real request (for example 'do I have X listed?', 'show me my Y', 'how many Z are left?'), so capture it. Pull their request from anywhere in the conversation, including across multiple turns: if they state a goal or problem early and then approve or narrow an action later (for example a brief 'yes, go ahead'), combine these into one request that keeps the action and its original subject and conditions, and capture that earlier request, not the bare confirmation. If they ask for several things, include every part, not just the one this tool handles. Stay strictly in the advertiser's own vocabulary: do not paraphrase, summarize, or shift their words into a more formal register; do not upgrade their plain words into domain or industry terms; do not phrase the request as the command or operation this tool performs; and do not add metric abbreviations or technical, product, or system field names they did not say themselves. This holds in every language: keep their phrasing in the language they used and never substitute the technical equivalent, translated or not, unless the advertiser used that term themselves. Leave this empty only when the message is pure greeting, small talk, thanks, or acknowledgment with no request of any kind; do not invent or infer a request that was not expressed. Do not include names, contact details, or other personal information.`, + }, + { + name: 'catalog_id', + type: 'string', + required: false, + description: `The product catalog ID (numeric string). Required unless product_set_id is provided.`, + }, + { + name: 'checks', + type: 'array', + required: false, + description: `Filter to specific check keys. If omitted, all checks for the scope are run. Catalog example keys: catalog_has_feed_upload_errors, pixel_has_low_event_source_match_rate. Product set example keys: product_set_has_low_quality_for_slideshow, product_set_has_low_quality_with_video.`, + }, + { + name: 'product_set_id', + type: 'string', + required: false, + description: `The product set ID (numeric string). Required unless catalog_id is provided. Takes precedence over catalog_id when both are supplied (catalog_id is ignored in that case). The owning catalog is resolved automatically from the product set.`, + }, + { + name: 'with_issue_only', + type: 'boolean', + required: false, + description: `When true (default), only return checks that have issues (failed). When false, return all checks including passed ones.`, + }, + ], + }, + { + name: 'facebookadsmcp_ads_catalog_get_feed_rules', + description: `Gets the data transformation rules applied to a product data feed during ingestion, with cursor-based pagination. + +Feed rules (also called "data feed rules", "feed transformation rules", or "supplementary data rules") control how product data from a feed file is modified, remapped, or enriched before it is stored in the catalog. Advertisers create these rules in Commerce Manager under Data Sources > Feed > Rules to fix data quality issues or adapt their feed format to Meta's required product fields. + +## Rule Types: +- **mapping_rule** — Renames or maps a column from the feed file to a Meta product field. For example, mapping a column named "product_name" to the standard "title" field. Use this when the user asks about column mappings, field mappings, renaming columns, or mapping feed columns. +- **value_mapping_rule** — Transforms specific values in a field to different values. For example, changing "yes"/"no" availability values to "in stock"/"out of stock". Use this when the user asks about value mappings, value transformations, or how values are being changed. +- **letter_case_rule** — Changes the letter case (capitalization) of a field's values. For example, converting product titles to title case or descriptions to lowercase. Use this when the user asks about capitalization, letter case, or case transformations. +- **fallback_rule** — Sets a default value for a product field when the feed does not provide one. For example, setting a default availability of "in stock" for products missing that field. Use this when the user asks about default values, fallback values, or missing field defaults. +- **regex_replace_rule** — Applies a regular expression find-and-replace on a field's values. For example, stripping HTML tags from descriptions or reformatting price strings. Use this when the user asks about regex rules, pattern replacements, or text find-and-replace transformations. + +## When to use: +- Call this tool when the user asks about feed rules, feed transformations, data transformation rules, or how their feed data is being modified — including questions about column/field mappings, value mappings, letter case, default/fallback values, or regex replacements (each maps to a rule type in the Rule Types section above). +- Use when the user asks "what rules are on my feed?", "how is my feed data being modified?", "why is my product data being changed during upload?", or "what transformations are applied to my feed?" +- Use when the user mentions "supplementary data rules" or "feed data manipulation." +- Use when debugging why product data in the catalog looks different from the original feed file. + +## When NOT to use: +- Do NOT call this tool for feed upload status, upload errors, schedule, or ingestion progress — use ads_catalog_get_product_feed_details instead. +- Do NOT call this tool for fetching products or product sets — use ads_catalog_search_product or ads_catalog_get_product_sets instead. +- Do NOT call this tool for catalog-level details — use ads_catalog_get_details instead. +- Do NOT call this tool for catalog diagnostics or quality issues — use ads_catalog_get_diagnostics instead. + +## Response Guidelines: +1. Present the list of rules including their ID, attribute (the product field being transformed), rule type, and parameters. +2. Explain each rule in plain language based on its rule type (see Rule Types above) and its parameters — e.g. which column maps to which Meta field, which values are converted, or what pattern is replaced. +3. If the feed is not found, inform the user that the feed ID may be invalid. +4. If page_info.has_next_page is true (and page_info.after_cursor is present), inform the user that more rules are available.`, + params: [ + { + name: 'feed_id', + type: 'string', + required: true, + description: `The ID of the product feed to list rules from.`, + }, + { + name: 'advertiser_request', + type: 'string', + required: false, + description: `Capture what the advertiser is actually asking for, in their exact words, quoted from their own messages word for word wherever you can. A question, lookup, or check counts as a real request (for example 'do I have X listed?', 'show me my Y', 'how many Z are left?'), so capture it. Pull their request from anywhere in the conversation, including across multiple turns: if they state a goal or problem early and then approve or narrow an action later (for example a brief 'yes, go ahead'), combine these into one request that keeps the action and its original subject and conditions, and capture that earlier request, not the bare confirmation. If they ask for several things, include every part, not just the one this tool handles. Stay strictly in the advertiser's own vocabulary: do not paraphrase, summarize, or shift their words into a more formal register; do not upgrade their plain words into domain or industry terms; do not phrase the request as the command or operation this tool performs; and do not add metric abbreviations or technical, product, or system field names they did not say themselves. This holds in every language: keep their phrasing in the language they used and never substitute the technical equivalent, translated or not, unless the advertiser used that term themselves. Leave this empty only when the message is pure greeting, small talk, thanks, or acknowledgment with no request of any kind; do not invent or infer a request that was not expressed. Do not include names, contact details, or other personal information.`, + }, + { + name: 'cursor', + type: 'string', + required: false, + description: `Pagination cursor for fetching the next page. Set this to the EXACT value of \`page_info.after_cursor\` from the previous response — copy the raw string as-is. Do NOT invent a cursor or use placeholder text. Omit entirely to start from page 1.`, + }, + { + name: 'limit', + type: 'integer', + required: false, + description: `Maximum number of rules to return (default: 20, max: 100).`, + }, + ], + }, + { + name: 'facebookadsmcp_ads_catalog_get_product_details', + description: `Fetches a product item from the catalog by its Meta-assigned product item ID (FBID). + +## Identifier types — read carefully before calling: +- \`product_id\` (this tool's input): Meta's internal canonical ID for a product item. **Numeric string only**, typically 15-19 digits, e.g., "26322317764065686". Globally unique across Meta. Assigned by Meta when the product is created/ingested. +- \`retailer_id\` (NOT accepted by this tool): The merchant-supplied SKU. **Alphanumeric**, may contain letters, digits, dashes, underscores — e.g., "ABC-001", "100MCaf198", "SKU-WIDGET-BLUE-L". Unique only within a single catalog. To look up a product by \`retailer_id\`, use \`ads_catalog_search_product\` with its JSON filter argument (e.g., {"retailer_id":{"eq":"ABC-001"}}). + +## When to use: +- The user provides a purely numeric ID (e.g., "26322317764065686") and asks for details about that product item. +- You already obtained a numeric \`product_id\` from a prior tool call (e.g., from \`ads_catalog_search_product\`) and need to fetch its full details. + +## When NOT to use: +- Do NOT call this tool when the user provides an alphanumeric identifier (anything containing letters, dashes, or that does not look like a long numeric FBID) — that is a \`retailer_id\`/SKU. Use \`ads_catalog_search_product\` with its JSON filter (e.g., {"retailer_id":{"eq":"ABC-001"}}) instead. +- Do NOT call this tool for listing or searching multiple products — use \`ads_catalog_search_product\`. +- Do NOT call this tool if you do not have a specific numeric product item ID. + +## Response Guidelines: +1. Present the product item details including ID, catalog ID, retailer ID, name, description, URL, price, availability, and image URL. When \`product_group_id\` (or \`retailer_product_group_id\`) is non-null, the item is a variant — present the product group ID so the user can see how variants are grouped. +2. If the user previously specified a catalog, verify the response's \`catalog_id\` matches before presenting the product — a mismatch means the \`product_item_id\` belongs to a different catalog and you should call this out instead of treating the result as authoritative for the user's catalog. +3. If the call fails because the input was not a positive numeric string, the user likely supplied a retailer ID (SKU). Retry with \`ads_catalog_search_product\` using its JSON filter (e.g., {"retailer_id":{"eq":""}}). +4. If the product item is not found despite a valid numeric ID, inform the user that the ID may be invalid or no longer exists in the catalog.`, + params: [ + { + name: 'product_id', + type: 'string', + required: true, + description: `Meta-assigned product item ID (FBID). Must be a positive numeric string only (e.g., "26322317764065686"). This is NOT the merchant's retailer_id/SKU — alphanumeric values like "ABC-001" or "100MCaf198" are retailer IDs and must be looked up via ads_catalog_search_product with its JSON filter (e.g., {"retailer_id":{"eq":"ABC-001"}}) instead.`, + }, + { + name: 'advertiser_request', + type: 'string', + required: false, + description: `Capture what the advertiser is actually asking for, in their exact words, quoted from their own messages word for word wherever you can. A question, lookup, or check counts as a real request (for example 'do I have X listed?', 'show me my Y', 'how many Z are left?'), so capture it. Pull their request from anywhere in the conversation, including across multiple turns: if they state a goal or problem early and then approve or narrow an action later (for example a brief 'yes, go ahead'), combine these into one request that keeps the action and its original subject and conditions, and capture that earlier request, not the bare confirmation. If they ask for several things, include every part, not just the one this tool handles. Stay strictly in the advertiser's own vocabulary: do not paraphrase, summarize, or shift their words into a more formal register; do not upgrade their plain words into domain or industry terms; do not phrase the request as the command or operation this tool performs; and do not add metric abbreviations or technical, product, or system field names they did not say themselves. This holds in every language: keep their phrasing in the language they used and never substitute the technical equivalent, translated or not, unless the advertiser used that term themselves. Leave this empty only when the message is pure greeting, small talk, thanks, or acknowledgment with no request of any kind; do not invent or infer a request that was not expressed. Do not include names, contact details, or other personal information.`, + }, + ], + }, + { + name: 'facebookadsmcp_ads_catalog_get_product_feed_details', + description: `Fetches details about a product feed, including its name, schedule configuration, product count, and upload session status. + +## When to use: +- Call this tool when the user asks about a product feed's configuration, schedule, or status. +- Use when the user wants to know how a feed is set up (e.g., upload frequency, source URL, timezone). +- Use when the user asks about feed ingestion source type (primary vs supplementary feed). +- Use when the user asks which feeds supplement a given feed. +- Use when the user asks about feed upload status, upload errors, or upload progress. +- Use when the user asks "is my feed upload still running?" or "did my last upload succeed?" + +## When NOT to use: +- Do NOT call this tool for feed rules, feed transformations, column mappings, value mappings, letter case changes, default/fallback values, or regex replacements — use ads_catalog_get_feed_rules instead. This tool only covers feed metadata and upload status, NOT data transformation rules. +- Do NOT call this tool to list a catalog's feeds or for a catalog-level overview — it needs a known feed_id and returns a single feed. Use ads_catalog_get_details (with feed_limit) or ads_catalog_get_data_sources instead. +- Do NOT call this tool for individual product details (use ads_catalog_search_product instead). +- Do NOT call this tool for catalog diagnostics or errors (use ads_catalog_get_diagnostics instead). + +## Input Requirements: +- **feed_id** must be a numeric string (e.g., "123456789") +- User must have access to the feed's catalog + +## Response Guidelines: +1. Present the feed details in a clear, structured format. +2. Clearly indicate if the feed has a schedule and what the upload frequency is. +3. If schedule is null, explain that the feed does not have an automatic upload schedule. +4. Distinguish between the replace schedule (full upload) and update schedule (incremental update) if both exist. +5. For upload sessions, highlight the result status (succeeded, failed, in_progress, etc.). +6. If there are errors or warnings in the upload, call them out prominently. +7. If an upload is in progress, indicate this clearly and show current progress counts. + +## Response Fields: +The JSON response includes: +- **feed_id**, **name**, **created_time** — feed identification +- **product_count** — number of items in this feed +- **ingestion_source_type** — "primary_feed" or "supplementary_feed" +- **override_type** — localization/override applied on top of a base feed (country, language, language_and_country, version, local), or null for a regular (non-override) feed +- **override_value** — the market/locale the override targets (e.g. "US" for a country feed, "fr_XX" for a language feed), or null when this is not an override feed +- **deletion_enabled** — whether missing items are deleted on upload +- **schedules** — array of upload schedules; each entry has type ("replace" for full upload or "update" for incremental update), interval, interval_count, hour, minute, day_of_week, timezone, url +- **latest_upload** — most recent upload session: feed_upload_session_id, result, is_in_progress, item counts (detected, persisted, invalid, deleted), error_count, warning_count +- **supplementary_feeds** — feeds that supplement this feed (each {feed_id, name, override_type, override_value}); populated only when this is a primary feed, empty otherwise +- **primary_feeds** — primary feeds this feed is attached to (each {feed_id, name, override_type, override_value}); populated only when this is a supplementary feed, empty otherwise`, + params: [ + { + name: 'feed_id', + type: 'string', + required: true, + description: `The product feed ID (numeric string).`, + }, + { + name: 'advertiser_request', + type: 'string', + required: false, + description: `Capture what the advertiser is actually asking for, in their exact words, quoted from their own messages word for word wherever you can. A question, lookup, or check counts as a real request (for example 'do I have X listed?', 'show me my Y', 'how many Z are left?'), so capture it. Pull their request from anywhere in the conversation, including across multiple turns: if they state a goal or problem early and then approve or narrow an action later (for example a brief 'yes, go ahead'), combine these into one request that keeps the action and its original subject and conditions, and capture that earlier request, not the bare confirmation. If they ask for several things, include every part, not just the one this tool handles. Stay strictly in the advertiser's own vocabulary: do not paraphrase, summarize, or shift their words into a more formal register; do not upgrade their plain words into domain or industry terms; do not phrase the request as the command or operation this tool performs; and do not add metric abbreviations or technical, product, or system field names they did not say themselves. This holds in every language: keep their phrasing in the language they used and never substitute the technical equivalent, translated or not, unless the advertiser used that term themselves. Leave this empty only when the message is pure greeting, small talk, thanks, or acknowledgment with no request of any kind; do not invent or infer a request that was not expressed. Do not include names, contact details, or other personal information.`, + }, + ], + }, + { + name: 'facebookadsmcp_ads_catalog_get_product_feed_upload_sessions', + description: `List recent upload sessions for a product feed, most recent first. Each session reports its outcome (status), timing (start/end), item counts (detected / persisted / invalid / deleted), and error/warning counts. Use this to debug feed ingestion — explain why products are missing or stale, when the last successful pull happened, or whether the most recent run failed. + +## When to use: +- The user asks why products from a feed are missing, stale, or were removed. +- The user wants to know when the feed last pulled successfully, or whether the latest run succeeded, partially uploaded, or failed. +- The user is debugging ingestion and wants the history of recent upload runs with their item counts and error/warning counts. + +## When NOT to use: +- The user wants to trigger a new refresh / re-pull of the feed — use \`ads_catalog_create_product_feed_upload_session\` instead. +- The user wants the feed's configuration (schedule, URL, source type) — use \`ads_catalog_get_product_feed_details\` instead. +- The user wants to inspect products directly — use \`ads_catalog_search_product\`. + +## Input Requirements: +- \`product_feed_id\` (required): The ID of the product feed. +- \`limit\` (optional): Maximum number of sessions to return. +- \`cursor\` (optional): Pagination cursor from a previous response. + +## Output Format: +JSON with: +- \`product_feed_id\` — echo of the input feed ID. +- \`sessions\` — list of upload sessions (most recent first) with status, timing, item counts, and error/warning counts. +- \`total_sessions\` — total number of upload sessions that exist for the feed, across all pages. +- \`has_more_sessions\` — whether more sessions exist beyond this page. +- \`next_session_cursor\` — cursor to fetch the next page, or null. + +## Errors: +- If the feed does not exist or the viewer cannot see it, the tool returns a \`feed_not_found\` error. +- A malformed (non-numeric) \`product_feed_id\` is rejected as an \`Invalid Params\` error before execution.`, + params: [ + { + name: 'product_feed_id', + type: 'string', + required: true, + description: `The ID of the product feed whose upload sessions to list.`, + }, + { + name: 'advertiser_request', + type: 'string', + required: false, + description: `Capture what the advertiser is actually asking for, in their exact words, quoted from their own messages word for word wherever you can. A question, lookup, or check counts as a real request (for example 'do I have X listed?', 'show me my Y', 'how many Z are left?'), so capture it. Pull their request from anywhere in the conversation, including across multiple turns: if they state a goal or problem early and then approve or narrow an action later (for example a brief 'yes, go ahead'), combine these into one request that keeps the action and its original subject and conditions, and capture that earlier request, not the bare confirmation. If they ask for several things, include every part, not just the one this tool handles. Stay strictly in the advertiser's own vocabulary: do not paraphrase, summarize, or shift their words into a more formal register; do not upgrade their plain words into domain or industry terms; do not phrase the request as the command or operation this tool performs; and do not add metric abbreviations or technical, product, or system field names they did not say themselves. This holds in every language: keep their phrasing in the language they used and never substitute the technical equivalent, translated or not, unless the advertiser used that term themselves. Leave this empty only when the message is pure greeting, small talk, thanks, or acknowledgment with no request of any kind; do not invent or infer a request that was not expressed. Do not include names, contact details, or other personal information.`, + }, + { + name: 'cursor', + type: 'string', + required: false, + description: `Pagination cursor from a previous response to fetch the next page of upload sessions. Set this to the EXACT value of next_session_cursor from the previous response. Omit to start from the most recent session.`, + }, + { + name: 'limit', + type: 'integer', + required: false, + description: `Maximum number of upload sessions to return, most recent first. Defaults to 20, capped at 100.`, + }, + ], + }, + { + name: 'facebookadsmcp_ads_catalog_get_product_product_sets', + description: `List the product sets that contain a given product item, with cursor-based pagination. Useful for understanding which sets (and downstream Advantage+ / DPA campaigns) a specific product appears in. + +When to use: +- To find which product sets include a specific product item +- To understand which campaigns might be affected by changes to a product +- To debug why a product appears or doesn't appear in certain product sets +- Use the page_info.after_cursor from the response to retrieve subsequent pages + +When NOT to use: +- To list all product sets in a catalog (use ads_catalog_get_product_sets) +- To get details about a specific product set (use ads_catalog_get_product_set_details) +- To list products within a product set (use ads_catalog_get_product_set_products)`, + params: [ + { + name: 'product_id', + type: 'string', + required: true, + description: `The ID of the product item to look up. Returns all product sets that contain this product.`, + }, + { + name: 'advertiser_request', + type: 'string', + required: false, + description: `Capture what the advertiser is actually asking for, in their exact words, quoted from their own messages word for word wherever you can. A question, lookup, or check counts as a real request (for example 'do I have X listed?', 'show me my Y', 'how many Z are left?'), so capture it. Pull their request from anywhere in the conversation, including across multiple turns: if they state a goal or problem early and then approve or narrow an action later (for example a brief 'yes, go ahead'), combine these into one request that keeps the action and its original subject and conditions, and capture that earlier request, not the bare confirmation. If they ask for several things, include every part, not just the one this tool handles. Stay strictly in the advertiser's own vocabulary: do not paraphrase, summarize, or shift their words into a more formal register; do not upgrade their plain words into domain or industry terms; do not phrase the request as the command or operation this tool performs; and do not add metric abbreviations or technical, product, or system field names they did not say themselves. This holds in every language: keep their phrasing in the language they used and never substitute the technical equivalent, translated or not, unless the advertiser used that term themselves. Leave this empty only when the message is pure greeting, small talk, thanks, or acknowledgment with no request of any kind; do not invent or infer a request that was not expressed. Do not include names, contact details, or other personal information.`, + }, + { + name: 'cursor', + type: 'string', + required: false, + description: `Pagination cursor for fetching the next page. Set this to the EXACT value of \`page_info.after_cursor\` from the previous response — copy the raw string as-is. Do NOT invent a cursor or use placeholder text. Omit entirely to start from page 1.`, + }, + { + name: 'limit', + type: 'integer', + required: false, + description: `Maximum number of product sets to return (default: 20).`, + }, + { + name: 'name', + type: 'string', + required: false, + description: `Optional. Case-insensitive substring match on product set name.`, + }, + ], + }, + { + name: 'facebookadsmcp_ads_catalog_get_product_set_details', + description: `Fetch details for a single product set by its ID, including name, filter rule, product count, type, visibility, and creation time. + +The product_set_id must be obtained from a prior ads_catalog_get_product_sets or ads_catalog_create_product_set call — do not guess or fabricate IDs. + +When to use: +- To inspect a specific product set's configuration and metadata +- To check the filter rule that defines which products belong to a set +- To verify product count, visibility, or type for a known product set ID +- After using ads_catalog_get_product_product_sets to drill into a specific set + +When NOT to use: +- To list all product sets in a catalog (use ads_catalog_get_product_sets) +- To list products within a product set (use ads_catalog_get_product_set_products) +- To find which product sets a product belongs to (use ads_catalog_get_product_product_sets)`, + params: [ + { + name: 'product_set_id', + type: 'string', + required: true, + description: `The ID of the product set to fetch details for. Must be obtained from a prior ads_catalog_get_product_sets or ads_catalog_create_product_set call — do not guess or fabricate IDs.`, + }, + { + name: 'advertiser_request', + type: 'string', + required: false, + description: `Capture what the advertiser is actually asking for, in their exact words, quoted from their own messages word for word wherever you can. A question, lookup, or check counts as a real request (for example 'do I have X listed?', 'show me my Y', 'how many Z are left?'), so capture it. Pull their request from anywhere in the conversation, including across multiple turns: if they state a goal or problem early and then approve or narrow an action later (for example a brief 'yes, go ahead'), combine these into one request that keeps the action and its original subject and conditions, and capture that earlier request, not the bare confirmation. If they ask for several things, include every part, not just the one this tool handles. Stay strictly in the advertiser's own vocabulary: do not paraphrase, summarize, or shift their words into a more formal register; do not upgrade their plain words into domain or industry terms; do not phrase the request as the command or operation this tool performs; and do not add metric abbreviations or technical, product, or system field names they did not say themselves. This holds in every language: keep their phrasing in the language they used and never substitute the technical equivalent, translated or not, unless the advertiser used that term themselves. Leave this empty only when the message is pure greeting, small talk, thanks, or acknowledgment with no request of any kind; do not invent or infer a request that was not expressed. Do not include names, contact details, or other personal information.`, + }, + ], + }, + { + name: 'facebookadsmcp_ads_catalog_get_product_set_products', + description: `Gets the products/items in a product set with cursor-based pagination and optional filters. + +## When to use: +- Call this tool when the user asks to see the products within a specific product set. +- Use when the user provides a product set ID and wants to list or browse the items in it. +- Use the page_info.after_cursor from the response to retrieve subsequent pages. +- Use the filter parameters (availability, retailer_id, brand, category, condition, product_type, price_min, price_max) to narrow the results within the product set. Filters are AND'd with the product set's defining rule. + +## When NOT to use: +- Do NOT call this tool for fetching details about a single product item (use ads_catalog_get_product_details instead). +- Do NOT call this tool for listing product sets in a catalog (use ads_catalog_get_product_sets instead). +- Do NOT call this tool if the user does not have a product set ID. +- Do NOT call this tool to delete products from a product set — it only lists products. To change which products belong to a set, update the product set filter rule with ads_catalog_update_product_set. + +## Response Guidelines: +1. Present the list of products using the fields returned in the response. +2. If a product includes \`catalog_id\`, verify it matches the catalog the user asked about before presenting results. +3. If the product set is not found, inform the user that the product set ID may be invalid. +4. If page_info.after_cursor is present, inform the user that more products are available.`, + params: [ + { + name: 'product_set_id', + type: 'string', + required: true, + description: `The ID of the product set to list products from.`, + }, + { + name: 'advertiser_request', + type: 'string', + required: false, + description: `Capture what the advertiser is actually asking for, in their exact words, quoted from their own messages word for word wherever you can. A question, lookup, or check counts as a real request (for example 'do I have X listed?', 'show me my Y', 'how many Z are left?'), so capture it. Pull their request from anywhere in the conversation, including across multiple turns: if they state a goal or problem early and then approve or narrow an action later (for example a brief 'yes, go ahead'), combine these into one request that keeps the action and its original subject and conditions, and capture that earlier request, not the bare confirmation. If they ask for several things, include every part, not just the one this tool handles. Stay strictly in the advertiser's own vocabulary: do not paraphrase, summarize, or shift their words into a more formal register; do not upgrade their plain words into domain or industry terms; do not phrase the request as the command or operation this tool performs; and do not add metric abbreviations or technical, product, or system field names they did not say themselves. This holds in every language: keep their phrasing in the language they used and never substitute the technical equivalent, translated or not, unless the advertiser used that term themselves. Leave this empty only when the message is pure greeting, small talk, thanks, or acknowledgment with no request of any kind; do not invent or infer a request that was not expressed. Do not include names, contact details, or other personal information.`, + }, + { + name: 'availability', + type: 'string', + required: false, + description: `Filter by availability status: in stock, out of stock, preorder, available for order, discontinued, pending, , mark_as_sold, mark_as_expired.`, + }, + { name: 'brand', type: 'string', required: false, description: `Filter by brand name.` }, + { + name: 'category', + type: 'string', + required: false, + description: `Filter by product category.`, + }, + { + name: 'condition', + type: 'string', + required: false, + description: `Filter by condition: new, refurbished, used.`, + }, + { + name: 'cursor', + type: 'string', + required: false, + description: `Pagination cursor for fetching the next page. Set this to the EXACT value of \`page_info.after_cursor\` from the previous response — copy the raw string as-is. Do NOT invent a cursor or use placeholder text. Omit entirely to start from page 1.`, + }, + { + name: 'fields', + type: 'array', + required: false, + description: `Optional top-level product fields to return. Omit or pass an empty array for the default fields: product_id, retailer_id, name, availability, price. If provided, only product_id plus the requested fields are returned. Supported values: product_id, catalog_id, retailer_id, product_group_id, retailer_product_group_id, name, description, url, price, sale_price, brand, category, color, condition, gender, material, pattern, size, availability, image_url, image_fetch_status, videos_fetch_status, visibility.`, + }, + { + name: 'limit', + type: 'integer', + required: false, + description: `Maximum number of products to return (default: 20, max: 100).`, + }, + { + name: 'price_max', + type: 'string', + required: false, + description: `Filter by maximum price.`, + }, + { + name: 'price_min', + type: 'string', + required: false, + description: `Filter by minimum price.`, + }, + { + name: 'product_type', + type: 'string', + required: false, + description: `Filter by product type.`, + }, + { + name: 'retailer_id', + type: 'string', + required: false, + description: `Filter by exact retailer ID (SKU) match. The retailer ID is the merchant-assigned alphanumeric identifier (e.g., "ABC-001", "100MCaf198", "SKU-WIDGET-BLUE-L") — not Meta's numeric product item ID. Match is case-sensitive. Use this filter when the user provides any non-numeric product identifier.`, + }, + ], + }, + { + name: 'facebookadsmcp_ads_catalog_get_product_sets', + description: `Gets a list of product sets in a catalog with cursor-based pagination. + +## When to use: +- Call this tool when the user asks to see the product sets within a catalog. +- Use when the user provides a catalog ID and wants to list or browse product sets. +- Use the page_info.after_cursor from the response to retrieve subsequent pages. + +## When NOT to use: +- Do NOT call this tool to count product sets (e.g., "how many product sets do I have") — use \`ads_catalog_get_details\` which returns \`product_set_count\` directly without enumerating the sets. +- Do NOT call this tool for fetching details about a specific product item. +- Do NOT call this tool if the user does not have a catalog ID. + +## Response Guidelines: +1. Present the list of product sets including their ID, name, product count, retailer ID, filter rule, type, visibility, and creation time. +2. If the catalog is not found, inform the user that the catalog ID may be invalid. +3. If page_info.has_next_page is true, inform the user that more product sets are available.`, + params: [ + { + name: 'catalog_id', + type: 'string', + required: true, + description: `The ID of the catalog to list product sets from.`, + }, + { + name: 'advertiser_request', + type: 'string', + required: false, + description: `Capture what the advertiser is actually asking for, in their exact words, quoted from their own messages word for word wherever you can. A question, lookup, or check counts as a real request (for example 'do I have X listed?', 'show me my Y', 'how many Z are left?'), so capture it. Pull their request from anywhere in the conversation, including across multiple turns: if they state a goal or problem early and then approve or narrow an action later (for example a brief 'yes, go ahead'), combine these into one request that keeps the action and its original subject and conditions, and capture that earlier request, not the bare confirmation. If they ask for several things, include every part, not just the one this tool handles. Stay strictly in the advertiser's own vocabulary: do not paraphrase, summarize, or shift their words into a more formal register; do not upgrade their plain words into domain or industry terms; do not phrase the request as the command or operation this tool performs; and do not add metric abbreviations or technical, product, or system field names they did not say themselves. This holds in every language: keep their phrasing in the language they used and never substitute the technical equivalent, translated or not, unless the advertiser used that term themselves. Leave this empty only when the message is pure greeting, small talk, thanks, or acknowledgment with no request of any kind; do not invent or infer a request that was not expressed. Do not include names, contact details, or other personal information.`, + }, + { + name: 'cursor', + type: 'string', + required: false, + description: `Pagination cursor for fetching the next page. Set this to the EXACT value of \`page_info.after_cursor\` from the previous response — copy the raw string as-is. Do NOT invent a cursor or use placeholder text. Omit entirely to start from page 1.`, + }, + { + name: 'limit', + type: 'integer', + required: false, + description: `Maximum number of product sets to return (default: 20, max: 100).`, + }, + { + name: 'name', + type: 'string', + required: false, + description: `Optional. Case-insensitive substring match on product set name. Combined with other filters via AND.`, + }, + ], + }, + { + name: 'facebookadsmcp_ads_catalog_product_create', + description: `Create a single catalog item. The item's vertical is determined by the target catalog: a commerce/products catalog gets a product item, a hotels catalog gets a hotel item, and so on. + +When to use: +- A merchant wants to add one item to an existing catalog by supplying its details. +- You already know the catalog ID (from ads_catalog_get_catalogs or ads_catalog_get_details). + +When NOT to use: +- To bulk-create a catalog and many items at once — use ads_catalog_create. +- To change an existing item — use ads_catalog_update_product (match on retailer_id). +- To create a product set (a saved filter) — use ads_catalog_create_product_set. + +Input Requirements: +- catalog_id (required): the owning catalog. Its vertical decides what kind of item is created. +- retailer_id (required): the merchant-unique SKU/content ID for the item. If an item with this retailer_id already exists it will be updated. +- name (required): the item's title/name. +- Other common product fields (description, url, image_url, price, sale_price, currency, availability, condition, brand, visibility) are optional but recommended for product/commerce catalogs; pair price with currency. +- properties (optional): a key->string map for any field not exposed as a dedicated key above — advanced/checkout product fields (e.g. checkout_url, quantity_to_sell_on_facebook, gtin, google_product_category, shipping, custom_label_0) and, for non-commerce catalogs, that vertical's fields. Keys use canonical feed field names. Do not duplicate a field already supplied via a dedicated key. + +Notes: +- Returns the retailer_id and catalog_id; use ads_catalog_search_product to look up the resulting item ID. +- For product (commerce) catalogs, mirrors the Graph API endpoint POST /{catalog_id}/products (single-product create).`, + params: [ + { + name: 'catalog_id', + type: 'string', + required: true, + description: `The ID of the catalog to add the product to (numeric string).`, + }, + { name: 'name', type: 'string', required: true, description: `The item title/name.` }, + { + name: 'retailer_id', + type: 'string', + required: true, + description: `The merchant-defined unique ID (SKU / content ID) for the new product.`, + }, + { + name: 'advertiser_request', + type: 'string', + required: false, + description: `Capture what the advertiser is actually asking for, in their exact words, quoted from their own messages word for word wherever you can. A question, lookup, or check counts as a real request (for example 'do I have X listed?', 'show me my Y', 'how many Z are left?'), so capture it. Pull their request from anywhere in the conversation, including across multiple turns: if they state a goal or problem early and then approve or narrow an action later (for example a brief 'yes, go ahead'), combine these into one request that keeps the action and its original subject and conditions, and capture that earlier request, not the bare confirmation. If they ask for several things, include every part, not just the one this tool handles. Stay strictly in the advertiser's own vocabulary: do not paraphrase, summarize, or shift their words into a more formal register; do not upgrade their plain words into domain or industry terms; do not phrase the request as the command or operation this tool performs; and do not add metric abbreviations or technical, product, or system field names they did not say themselves. This holds in every language: keep their phrasing in the language they used and never substitute the technical equivalent, translated or not, unless the advertiser used that term themselves. Leave this empty only when the message is pure greeting, small talk, thanks, or acknowledgment with no request of any kind; do not invent or infer a request that was not expressed. Do not include names, contact details, or other personal information.`, + }, + { + name: 'availability', + type: 'string', + required: false, + description: `Stock availability (e.g. "in stock", "out of stock", "preorder").`, + }, + { name: 'brand', type: 'string', required: false, description: `The product brand.` }, + { + name: 'condition', + type: 'string', + required: false, + description: `Product condition (e.g. "new", "refurbished", "used").`, + }, + { + name: 'currency', + type: 'string', + required: false, + description: `The ISO currency code for price/sale_price (e.g. "USD").`, + }, + { + name: 'description', + type: 'string', + required: false, + description: `The product description.`, + }, + { name: 'image_url', type: 'string', required: false, description: `The product image URL.` }, + { + name: 'price', + type: 'string', + required: false, + description: `The product price amount (e.g. "9.99"). Pair with currency.`, + }, + { + name: 'properties', + type: 'object', + required: false, + description: `Additional item fields as a key->string map, using canonical feed field names (e.g. checkout_url, quantity_to_sell_on_facebook, gtin, google_product_category, shipping, custom_label_0, size, color). Use this for advanced/checkout fields not exposed as dedicated keys above, and for the vertical-specific fields when the target catalog is not a commerce/products catalog. Do NOT duplicate a field already provided via a dedicated key.`, + }, + { + name: 'sale_price', + type: 'string', + required: false, + description: `The product sale price amount (e.g. "7.99").`, + }, + { + name: 'url', + type: 'string', + required: false, + description: `The product landing-page URL.`, + }, + { + name: 'visibility', + type: 'string', + required: false, + description: `Product visibility ("published" to show, "staging" to hide).`, + }, + ], + }, + { + name: 'facebookadsmcp_ads_catalog_product_feed_delete', + description: `Delete a product feed (data source) from a catalog. The feed and its schedule are removed, and the products it ingested are deleted from the catalog as well (items also supplied by another data source are kept). Product removal happens asynchronously. + +When to use: +- A merchant wants to remove a feed data source and the products it brought into the catalog. +- You already know the feed ID (from ads_catalog_get_data_sources or ads_catalog_get_product_feed_details). + +When NOT to use: +- To stop/pause/turn off a feed's automatic refreshing without removing the feed — this is NOT a deletion. Use ads_catalog_update_product_feed with clear_replace_schedule (and/or clear_update_schedule) to stop the schedule; the feed and its products are kept. +- To delete a single product rather than the whole feed — use ads_catalog_delete_product. +- To delete an entire catalog — needs to be done from Commerce Manager. + +Notes: +- This action cannot be undone through the API. Treat it as permanent. +- Product removal is asynchronous, so catalog item counts may take a little while to update.`, + params: [ + { + name: 'product_feed_id', + type: 'string', + required: true, + description: `The ID of the product feed (data source) to delete (numeric string).`, + }, + { + name: 'advertiser_request', + type: 'string', + required: false, + description: `Capture what the advertiser is actually asking for, in their exact words, quoted from their own messages word for word wherever you can. A question, lookup, or check counts as a real request (for example 'do I have X listed?', 'show me my Y', 'how many Z are left?'), so capture it. Pull their request from anywhere in the conversation, including across multiple turns: if they state a goal or problem early and then approve or narrow an action later (for example a brief 'yes, go ahead'), combine these into one request that keeps the action and its original subject and conditions, and capture that earlier request, not the bare confirmation. If they ask for several things, include every part, not just the one this tool handles. Stay strictly in the advertiser's own vocabulary: do not paraphrase, summarize, or shift their words into a more formal register; do not upgrade their plain words into domain or industry terms; do not phrase the request as the command or operation this tool performs; and do not add metric abbreviations or technical, product, or system field names they did not say themselves. This holds in every language: keep their phrasing in the language they used and never substitute the technical equivalent, translated or not, unless the advertiser used that term themselves. Leave this empty only when the message is pure greeting, small talk, thanks, or acknowledgment with no request of any kind; do not invent or infer a request that was not expressed. Do not include names, contact details, or other personal information.`, + }, + ], + }, + { + name: 'facebookadsmcp_ads_catalog_product_feed_delete_rule', + description: `Permanently delete a transformation rule from a product feed. This action is irreversible — the rule is removed and no longer applied to future feed ingestions. The feed and its products are not affected; only the transformation rule is removed. + +When to use: +- To remove a feed rule that is no longer needed +- To clean up a feed rule created in error or with the wrong configuration (attribute and rule_type are immutable, so a misconfigured rule must be deleted and recreated) + +When NOT to use: +- To change a rule's parameters — update the rule instead (its params can be modified without deleting it) +- To inspect existing rules on a feed (use ads_catalog_get_feed_rules) +- To delete a product set, product, feed, or entire catalog — use the dedicated tools. + +Notes: +- Only the transformation rule is deleted; feed data and products are left unchanged.`, + params: [ + { + name: 'feed_rule_id', + type: 'string', + required: true, + description: `The ID of the feed rule to delete.`, + }, + { + name: 'advertiser_request', + type: 'string', + required: false, + description: `Capture what the advertiser is actually asking for, in their exact words, quoted from their own messages word for word wherever you can. A question, lookup, or check counts as a real request (for example 'do I have X listed?', 'show me my Y', 'how many Z are left?'), so capture it. Pull their request from anywhere in the conversation, including across multiple turns: if they state a goal or problem early and then approve or narrow an action later (for example a brief 'yes, go ahead'), combine these into one request that keeps the action and its original subject and conditions, and capture that earlier request, not the bare confirmation. If they ask for several things, include every part, not just the one this tool handles. Stay strictly in the advertiser's own vocabulary: do not paraphrase, summarize, or shift their words into a more formal register; do not upgrade their plain words into domain or industry terms; do not phrase the request as the command or operation this tool performs; and do not add metric abbreviations or technical, product, or system field names they did not say themselves. This holds in every language: keep their phrasing in the language they used and never substitute the technical equivalent, translated or not, unless the advertiser used that term themselves. Leave this empty only when the message is pure greeting, small talk, thanks, or acknowledgment with no request of any kind; do not invent or infer a request that was not expressed. Do not include names, contact details, or other personal information.`, + }, + ], + }, + { + name: 'facebookadsmcp_ads_catalog_product_set_delete', + description: `Permanently delete a product set from a catalog. This action is irreversible — the product set and its filter rule are removed. Products themselves are not deleted; only the set that groups them. + +When to use: +- To remove a product set that is no longer needed +- To clean up product sets created in error + +When NOT to use: +- To change which products a dynamic set contains — adjust its filter rule (use ads_catalog_update_product_set) +- To rename a product set (use ads_catalog_update_product_set) +- To delete one or more products or entire catalog — use the dedicated tools. + +Notes: +- Only leaf product sets can be deleted. A set that has child sets cannot be deleted until its child sets are removed first. +- A product set that is in use by an active usage (e.g. a live ad) cannot be deleted until that usage is deactivated or detached. This tool does not modify ads or other usages.`, + params: [ + { + name: 'product_set_id', + type: 'string', + required: true, + description: `The ID of the product set to delete.`, + }, + { + name: 'advertiser_request', + type: 'string', + required: false, + description: `Capture what the advertiser is actually asking for, in their exact words, quoted from their own messages word for word wherever you can. A question, lookup, or check counts as a real request (for example 'do I have X listed?', 'show me my Y', 'how many Z are left?'), so capture it. Pull their request from anywhere in the conversation, including across multiple turns: if they state a goal or problem early and then approve or narrow an action later (for example a brief 'yes, go ahead'), combine these into one request that keeps the action and its original subject and conditions, and capture that earlier request, not the bare confirmation. If they ask for several things, include every part, not just the one this tool handles. Stay strictly in the advertiser's own vocabulary: do not paraphrase, summarize, or shift their words into a more formal register; do not upgrade their plain words into domain or industry terms; do not phrase the request as the command or operation this tool performs; and do not add metric abbreviations or technical, product, or system field names they did not say themselves. This holds in every language: keep their phrasing in the language they used and never substitute the technical equivalent, translated or not, unless the advertiser used that term themselves. Leave this empty only when the message is pure greeting, small talk, thanks, or acknowledgment with no request of any kind; do not invent or infer a request that was not expressed. Do not include names, contact details, or other personal information.`, + }, + ], + }, + { + name: 'facebookadsmcp_ads_catalog_search_product', + description: `Searches or lists products in a catalog using a structured filter rule (see **Filter spec** below) and returns sample matching products plus the **total** number of products that match. Use this for all catalog product listing, searching, and filtering — including lookups by retailer_id/SKU, availability, brand, category, price range, and custom labels — and BEFORE creating a product set with \`ads_catalog_create_product_set\` to preview which products the candidate filter resolves to. + +## Identifier types — read carefully before calling: +- \`retailer_id\`: The merchant-supplied SKU. **Alphanumeric**, may contain letters, digits, dashes, underscores — e.g., "ABC-001", "100MCaf198", "SKU-WIDGET-BLUE-L". Unique within a single catalog. Use \`{"retailer_id":{"eq":"ABC-001"}}\`. **If the user supplies any non-numeric identifier (contains letters, dashes, etc.), treat it as a \`retailer_id\`.** +- Meta's numeric \`product_id\` (FBID, e.g., "26322317764065686") is returned in the response as the \`product_id\` field. To fetch a product by its numeric FBID directly, use \`ads_catalog_get_product_details\` instead. + +## When to use: +- The user wants to list, browse, or search products in their catalog (with or without filters). +- The user wants to know how many products match a candidate filter before creating a product set ("how many in-stock Acme items are there?"). +- The user wants to see sample products matching a filter to sanity-check the filter is correct. +- The user wants to list (or count) the products ingested by a specific feed / data source ("show me products from my main feed", "which items did feed 1234567890 add?", "how many products came from this data feed?") — filter by \`product_feed_id\` and read \`page_info.total_count\` for the count. +- Looking up a specific product by retailer_id/SKU. As a fallback when \`ads_catalog_get_product_details\` rejects an input as not a positive numeric string — the input was likely a retailer_id; retry here. +- Debugging product-level issues (missing fields, incorrect prices, availability, broken images via \`image_fetch_status\`). + +## When NOT to use: +- Fetching a specific product by Meta numeric FBID — use \`ads_catalog_get_product_details\`. This applies to ANY purely-numeric identifier the user provides as a "product ID" or "product item ID", regardless of length (FBIDs may appear short like "12345" or long like "26322317764065686"). +- Fetching products in an existing product set — use \`ads_catalog_get_product_set_products\`. +- Catalog diagnostics — use \`ads_catalog_get_diagnostics\`. +- Catalog overview — use \`ads_catalog_get_details\`. +- There is no query text-search parameter — use the structured filter rule instead (e.g. \`{"name":{"contains":"red dress"}}\`). + +## Input: +- \`catalog_id\` (required): the product catalog ID. +- \`filter\` (required): a JSON-encoded rule that matches products by their attributes. See the **Filter spec** section below for the full operator catalog and examples. +- \`limit\` (optional, default 20, max 100): number of sample products to return. +- \`cursor\` (optional): pagination cursor from the previous response. +- \`fields\` (optional): product fields to return. Omit or pass [] for default fields; explicit values return \`product_id\` plus requested fields. +- \`error_type\` (optional): a catalog diagnostic error type (e.g. \`PRODUCT_NOT_APPROVED\`) to return only products currently affected by that error. This is a SEPARATE parameter — do NOT place \`error_type\` inside the \`filter\` JSON. When both \`filter\` and \`error_type\` are supplied, they are combined with AND. + +## Filter spec: +**Shape:** +- Leaf rule: \`{: {: }}\` +- Compound rule: \`{: [, ...]}\` + +**Logical combinators:** +- \`and\` — match ALL of the nested rules. Example: \`{"and":[{"availability":{"eq":"in stock"}},{"brand":{"eq":"Acme"}}]}\` returns items that are in stock AND Acme-branded. +- \`or\` — match ANY of the nested rules. Example: \`{"or":[{"category":{"contains":"shoe"}},{"category":{"contains":"sneaker"}}]}\` returns items whose category contains either "shoe" or "sneaker". +- \`not\` — invert a single rule. Example: \`{"not":{"brand":{"eq":"Acme"}}}\` returns items whose brand is NOT exactly "Acme". Wraps one rule, not an array. + +**Comparison operators:** +- \`eq\` — exact match. Example: \`{"brand":{"eq":"Instagram"}}\` matches only items with brand exactly "Instagram". +- \`neq\` — does NOT exactly match. Example: \`{"brand":{"neq":"Instagram"}}\` matches items whose brand is anything other than "Instagram". +- \`lt\`, \`lte\` — numeric less-than (strict / inclusive). Example: \`{"priority":{"lt":3}}\` matches items with priority < 3. +- \`gt\`, \`gte\` — numeric greater-than (strict / inclusive). Example: \`{"priority":{"gte":3}}\` matches items with priority >= 3. + +**String operators:** +- \`contains\` — substring match. Example: \`{"category":{"contains":"running shoe"}}\` matches items whose category contains the substring, e.g. "red running shoe", "blue running shoe", "running shoe for kids". +- \`not_contains\` — substring excludes. Example: \`{"category":{"not_contains":"running shoe"}}\` matches items whose category does NOT contain the substring, e.g. "red walking shoe", "sandals", "boots". +- \`starts_with\` — prefix match. Example: \`{"category":{"starts_with":"small"}}\` matches "small sandals", "small t-shirt", etc. **Note:** only valid for the product category field; for other fields use \`contains\`. + +**Set operators:** (right-hand side is an array) +- \`is_any\` — match if value is any one of the listed. Example: \`{"color":{"is_any":["black","blue","brown"]}}\` matches items in any of those colors. +- \`is_not_any\` — match if value is none of the listed. Example: \`{"color":{"is_not_any":["black","blue","brown"]}}\` matches items NOT in any of those colors (e.g. "red", "yellow", "green"). + +**Supported fields (commerce vertical only):** + +- \`age_group\` — string enum, one of: \`adult\`, \`infant\`, \`kids\`, \`newborn\`, \`toddler\`. Use \`eq\` / \`is_any\`. +- \`availability\` — string enum, one of: \`available for order\`, \`in stock\`, \`preorder\`, \`out of stock\`. Use \`eq\` / \`neq\` / \`is_any\` / \`is_not_any\`. +- \`brand\` — string. Brand name from the feed. String operators apply. +- \`category\` — string. Free-form merchant category from the feed (e.g. \`"running shoe"\`). String operators apply (including \`starts_with\`). +- \`color\` — string. String operators apply. +- \`condition\` — string enum, one of: \`new\`, \`refurbished\`, \`used\`. Use \`eq\` / \`neq\` / \`is_any\` / \`is_not_any\`. +- \`currency\` — string ISO-4217 currency code (e.g. \`"USD"\`, \`"GBP"\`). Use \`eq\` / \`is_any\`. +- \`custom_label_0\`, \`custom_label_1\`, \`custom_label_2\`, \`custom_label_3\`, \`custom_label_4\` — string. Free-form merchant labels from the feed. String operators apply. +- \`gender\` — string enum, one of: \`female\`, \`male\`, \`unisex\`. Use \`eq\` / \`is_any\`. +- \`images_fetch_status\` — string. Fetch status of the product's images. Common values: \`fetched\`, \`direct_upload\`, \`fetch_failed\`, \`outdated\`, \`partial_fetch\`, \`not_fetched\`. Use \`eq\` / \`is_any\` (e.g. find products with broken images: \`{"images_fetch_status":{"eq":"fetch_failed"}}\`). Note: the filter field is plural \`images_fetch_status\`, even though the returned response field is singular \`image_fetch_status\`. +- \`material\` — string. String operators apply. +- \`name\` — string. Product name/title from the feed. String operators apply. +- \`pattern\` — string (e.g. \`"striped"\`, \`"polka dot"\`). String operators apply. +- \`price_amount\` — integer; the price multiplied by 100, for all currencies (e.g. \`$4.90 USD\` → \`490\`, \`¥490 JPY\` → \`49000\`). Use numeric operators (\`eq\`, \`lt\`, \`lte\`, \`gt\`, \`gte\`). Note: the field is \`price_amount\`, not \`price\`. +- \`product_expiration_time\` — date/time when the product is no longer available. +- \`product_feed_id\` — integer. The ID for the product feed. Use \`eq\` / \`is_any\`. +- \`product_group_id\` — integer. ID grouping product variants (e.g. all sizes/colors of one shirt share a \`product_group_id\`). Use \`eq\` / \`is_any\`. +- \`product_item_id\` — integer. Meta-assigned numeric product item ID. Use \`eq\` / \`is_any\` for exact lookup. +- \`product_type\` — string. Merchant-defined taxonomy (e.g. \`"Apparel & Accessories > Shoes"\`). String operators apply. +- \`region_id\` — integer. The region ID for the location for a product item. Use \`eq\` / \`is_any\`. +- \`retailer_id\` — string. The merchant-provided unique identifier (SKU). Use \`eq\`, \`is_any\`, etc. for exact matches. +- \`retailer_product_group_id\` — string. The merchant-provided identifier for the product group the item belongs to (the retailer's item group ID). Use \`eq\`, \`is_any\`, etc. for exact matches. +- \`sale_price_amount\` — integer; same format as \`price_amount\` (price × 100). Numeric operators apply. Note: the field is \`sale_price_amount\`, not \`sale_price\`. +- \`size\` — string. String operators apply. +- \`tags\` — string. Tags for product organization. String operators apply. +- \`videos_fetch_status\` — string. The fetch status of associated videos. Use \`eq\` / \`is_any\`. +- \`visibility\` — string enum, one of: \`published\`, \`staging\`, \`hidden\`, \`whitelist_only\`. Items in \`staging\` are not visible to buyers and are not available in dynamic ads. Use \`eq\` / \`is_any\`. + +**Notes:** +- Only the commerce vertical is supported. Field names not in the list above will be rejected. +- Use \`eq\` (not \`contains\`) for enum-typed fields (\`age_group\`, \`availability\`, \`condition\`, \`gender\`, \`visibility\`). +- Value matching is case-insensitive: \`{"brand":{"eq":"acme"}}\` and \`{"brand":{"eq":"Acme"}}\` return the same items. Do not bother trying multiple casings of the same value to "broaden" matches. +- \`price_amount\` and \`sale_price_amount\` are integers — the price multiplied by 100. \`{"price_amount":{"lt":"5000"}}\` means "less than $50.00", not "less than $5000". Bare \`price\` / \`sale_price\` are not valid filter fields for product items (they apply to other verticals only). + +**Common wrong field name aliases:** +- \`title\`, \`product_name\` → use \`name\` (the product title/name field). +- \`price\`, \`sale_price\`, \`current_price\` → use \`price_amount\` / \`sale_price_amount\` (integers, price × 100). +- \`item_group_id\` → use \`product_group_id\`. +- \`product_id\` → use \`product_item_id\` (Meta-assigned numeric ID). +- \`image_fetch_status\` (singular) → use \`images_fetch_status\` (plural). +- \`link\`, \`description\`, \`sku\` → not supported filter fields. Use \`retailer_id\` for SKU lookups. + +## Returns: +JSON with: +- \`products\`: array [{product_id, catalog_id, name, price, availability, brand, retailer_id, ...}] — a SAMPLE of matching products. \`product_id\` is Meta's numeric product item FBID. If \`catalog_id\` is returned, it echoes the request \`catalog_id\` so you can confirm the result is scoped to the catalog you asked about. When \`fields\` is provided, products include only \`product_id\` plus requested fields. +- \`page_info\`: object with \`after_cursor\` (string), \`has_next_page\` (bool), and \`total_count\` (int — the total number of products matching the filter, NOT just the count in this page). + +## Pagination: +To fetch the next page of samples, call this tool again with the SAME \`catalog_id\` and \`filter\`, and set \`cursor\` to the value of \`page_info.after_cursor\` from the previous response. Only paginate while \`page_info.has_next_page\` is \`true\`. + +## Examples: +- "What products are in my catalog?" → filter=\`{}\` (empty filter returns all products). +- "List out-of-stock products" → filter=\`{"availability":{"eq":"out of stock"}}\`. +- "Find product ABC-001" → filter=\`{"retailer_id":{"eq":"ABC-001"}}\`. +- "Fetch description of product item 100MCaf198" → filter=\`{"retailer_id":{"eq":"100MCaf198"}}\` (alphanumeric → SKU, not FBID). +- "How many in-stock items in catalog 123?" → filter=\`{"availability":{"eq":"in stock"}}\`, then read \`page_info.total_count\`. +- "List products ingested by feed 1234567890" → filter=\`{"product_feed_id":{"eq":1234567890}}\` (numeric feed ID; read \`page_info.total_count\` for "how many products did this feed ingest?"). +- "Show in-stock products from feed 1234567890" → filter=\`{"and":[{"product_feed_id":{"eq":1234567890}},{"availability":{"eq":"in stock"}}]}\`. +- "Show me 10 sample shoes priced under $50" → filter=\`{"and":[{"category":{"contains":"shoe"}},{"price_amount":{"lt":5000}}]}\`, limit=10. +- "Which products have broken/failed images?" → filter=\`{"images_fetch_status":{"eq":"fetch_failed"}}\`. +- "Which products are not approved?" → filter=\`{}\`, error_type=\`PRODUCT_NOT_APPROVED\` (pass error_type as its own argument, not inside filter). +- "List in-stock products that are not approved" → filter=\`{"availability":{"eq":"in stock"}}\`, error_type=\`PRODUCT_NOT_APPROVED\`. + +## Pixel event matching workflow: +When the user asks "how will this pixel event be matched?" or "which products will match this pixel fire?" given a pixel content ID: +1. Call \`ads_catalog_event_source_get_catalogs\` with the pixel (event source) ID to retrieve the catalog IDs connected to that pixel. +2. For each returned \`catalog_id\`, call \`ads_catalog_search_product\` with \`filter={"retailer_id":{"eq":""}}\` — the pixel \`content_id\` maps to \`retailer_id\` in the catalog. +The products found across all connected catalogs are the ones that would be matched when the pixel fires with that content ID.`, + params: [ + { + name: 'catalog_id', + type: 'string', + required: true, + description: `The product catalog ID.`, + }, + { + name: 'filter', + type: 'string', + required: true, + description: `Required. JSON filter rule — pass {} (empty object) to list all products. See the **Filter spec** section in this tool's description for supported operators and fields.`, + }, + { + name: 'advertiser_request', + type: 'string', + required: false, + description: `Capture what the advertiser is actually asking for, in their exact words, quoted from their own messages word for word wherever you can. A question, lookup, or check counts as a real request (for example 'do I have X listed?', 'show me my Y', 'how many Z are left?'), so capture it. Pull their request from anywhere in the conversation, including across multiple turns: if they state a goal or problem early and then approve or narrow an action later (for example a brief 'yes, go ahead'), combine these into one request that keeps the action and its original subject and conditions, and capture that earlier request, not the bare confirmation. If they ask for several things, include every part, not just the one this tool handles. Stay strictly in the advertiser's own vocabulary: do not paraphrase, summarize, or shift their words into a more formal register; do not upgrade their plain words into domain or industry terms; do not phrase the request as the command or operation this tool performs; and do not add metric abbreviations or technical, product, or system field names they did not say themselves. This holds in every language: keep their phrasing in the language they used and never substitute the technical equivalent, translated or not, unless the advertiser used that term themselves. Leave this empty only when the message is pure greeting, small talk, thanks, or acknowledgment with no request of any kind; do not invent or infer a request that was not expressed. Do not include names, contact details, or other personal information.`, + }, + { + name: 'cursor', + type: 'string', + required: false, + description: `Pagination cursor for fetching the next page. Set this to the EXACT value of \`page_info.after_cursor\` from the previous response — copy the raw string as-is. Do NOT invent a cursor or use placeholder text. Omit entirely to start from page 1.`, + }, + { + name: 'error_type', + type: 'string', + required: false, + description: `Optional diagnostic error type to filter products by (e.g. \`PRODUCT_NOT_APPROVED\`). Returns only products currently affected by the given diagnostic error. Use the \`type\` value reported by \`ads_catalog_get_diagnostics\`. Note: not every diagnostic type is filterable here — an unfilterable value is rejected with an error rather than silently ignored. This is a separate parameter from \`filter\` — it is NOT a product-set filter field, so do not put \`error_type\` inside the \`filter\` JSON. Combined with \`filter\` using AND when both are provided.`, + }, + { + name: 'fields', + type: 'array', + required: false, + description: `Optional top-level product fields to return. Omit or pass an empty array for the default fields: product_id, retailer_id, name, availability, price. If provided, only product_id plus the requested fields are returned. Supported values: product_id, catalog_id, retailer_id, product_group_id, retailer_product_group_id, name, description, url, price, sale_price, brand, category, color, condition, gender, material, pattern, size, availability, image_url, image_fetch_status, videos_fetch_status, visibility.`, + }, + { + name: 'limit', + type: 'integer', + required: false, + description: `Maximum number of sample products to return (default: 20, max: 100).`, + }, + ], + }, + { + name: 'facebookadsmcp_ads_catalog_update_catalog', + description: `Update an existing catalog's settings. At least one field besides catalog_id must be provided. + +When to use: +- To rename a catalog + +When NOT to use: +- To create a new catalog (use ads_catalog_create) +- To view catalog details without changing anything (use ads_catalog_get_details) +- To delete a catalog — not supported via MCP`, + params: [ + { + name: 'catalog_id', + type: 'string', + required: true, + description: `The ID of the catalog to update.`, + }, + { + name: 'advertiser_request', + type: 'string', + required: false, + description: `Capture what the advertiser is actually asking for, in their exact words, quoted from their own messages word for word wherever you can. A question, lookup, or check counts as a real request (for example 'do I have X listed?', 'show me my Y', 'how many Z are left?'), so capture it. Pull their request from anywhere in the conversation, including across multiple turns: if they state a goal or problem early and then approve or narrow an action later (for example a brief 'yes, go ahead'), combine these into one request that keeps the action and its original subject and conditions, and capture that earlier request, not the bare confirmation. If they ask for several things, include every part, not just the one this tool handles. Stay strictly in the advertiser's own vocabulary: do not paraphrase, summarize, or shift their words into a more formal register; do not upgrade their plain words into domain or industry terms; do not phrase the request as the command or operation this tool performs; and do not add metric abbreviations or technical, product, or system field names they did not say themselves. This holds in every language: keep their phrasing in the language they used and never substitute the technical equivalent, translated or not, unless the advertiser used that term themselves. Leave this empty only when the message is pure greeting, small talk, thanks, or acknowledgment with no request of any kind; do not invent or infer a request that was not expressed. Do not include names, contact details, or other personal information.`, + }, + { name: 'name', type: 'string', required: false, description: `New name for the catalog.` }, + ], + }, + { + name: 'facebookadsmcp_ads_catalog_update_product', + description: `Update one or more fields on an existing product item (catalog product). Only the fields you provide are changed; omitted fields are left untouched. + +When to use: +- A merchant wants to correct or change a product's name, description, landing-page URL, image URL, brand, availability, condition, price, sale price, or visibility. +- A merchant wants to hide a product (without permanently deleting it) — set visibility="hidden" (and visibility="published" to make it visible again). +- You already know the product item ID (from ads_catalog_search_product or ads_catalog_get_product_details). + +When NOT to use: +- To create a new product (this tool only updates existing items). +- To inspect a product's current values (use ads_catalog_get_product_details). +- To edit product sets, feeds, or the catalog itself (use the dedicated tools). + +Notes: +- price and sale_price are decimal amount strings in major currency units (e.g. "19.99"), NOT cents, and require currency (ISO-4217, e.g. "USD") to be set in the same call. +- availability must be one of: "in stock", "out of stock", "preorder", "available for order", "discontinued". +- condition must be one of: "new", "refurbished", "used", "used_like_new", "used_good", "used_fair", "cpo", "open_box_new". +- visibility must be one of: "published", "hidden". A "hidden" item is not visible to buyers and not eligible for dynamic ads — this is the reversible way to hide a product instead of deleting it; set it back to "published" to restore it. +- At least one updatable field must be provided. +- Mirrors the Graph API endpoint POST /{product_item_id}.`, + params: [ + { + name: 'product_id', + type: 'string', + required: true, + description: `The ID of the product item to update (numeric string).`, + }, + { + name: 'advertiser_request', + type: 'string', + required: false, + description: `Capture what the advertiser is actually asking for, in their exact words, quoted from their own messages word for word wherever you can. A question, lookup, or check counts as a real request (for example 'do I have X listed?', 'show me my Y', 'how many Z are left?'), so capture it. Pull their request from anywhere in the conversation, including across multiple turns: if they state a goal or problem early and then approve or narrow an action later (for example a brief 'yes, go ahead'), combine these into one request that keeps the action and its original subject and conditions, and capture that earlier request, not the bare confirmation. If they ask for several things, include every part, not just the one this tool handles. Stay strictly in the advertiser's own vocabulary: do not paraphrase, summarize, or shift their words into a more formal register; do not upgrade their plain words into domain or industry terms; do not phrase the request as the command or operation this tool performs; and do not add metric abbreviations or technical, product, or system field names they did not say themselves. This holds in every language: keep their phrasing in the language they used and never substitute the technical equivalent, translated or not, unless the advertiser used that term themselves. Leave this empty only when the message is pure greeting, small talk, thanks, or acknowledgment with no request of any kind; do not invent or infer a request that was not expressed. Do not include names, contact details, or other personal information.`, + }, + { + name: 'availability', + type: 'string', + required: false, + description: `New availability status. One of: "in stock", "out of stock", "preorder", "available for order", "discontinued". Omit to leave it unchanged.`, + }, + { + name: 'brand', + type: 'string', + required: false, + description: `New brand name. Omit to leave the current brand unchanged.`, + }, + { + name: 'condition', + type: 'string', + required: false, + description: `New item condition. One of: "new", "refurbished", "used", "used_like_new", "used_good", "used_fair", "cpo", "open_box_new". Omit to leave it unchanged.`, + }, + { + name: 'currency', + type: 'string', + required: false, + description: `ISO-4217 currency code (e.g. "USD", "GBP") for price and sale_price. Required when price or sale_price is provided; ignored otherwise.`, + }, + { + name: 'description', + type: 'string', + required: false, + description: `New product description. Omit to leave the current description unchanged.`, + }, + { + name: 'image_url', + type: 'string', + required: false, + description: `New primary image URL for the product. Omit to leave it unchanged.`, + }, + { + name: 'name', + type: 'string', + required: false, + description: `New product name/title. Omit to leave the current name unchanged.`, + }, + { + name: 'price', + type: 'string', + required: false, + description: `New price as a decimal amount in major currency units (e.g. "19.99"), NOT cents. Requires currency to be set in the same call. Omit to leave the price unchanged.`, + }, + { + name: 'sale_price', + type: 'string', + required: false, + description: `New sale price as a decimal amount in major currency units (e.g. "14.99"), NOT cents. Requires currency to be set in the same call. Omit to leave the sale price unchanged.`, + }, + { + name: 'url', + type: 'string', + required: false, + description: `New product landing-page URL (the link buyers open). Omit to leave it unchanged.`, + }, + { + name: 'visibility', + type: 'string', + required: false, + description: `New visibility status. One of: "published" (visible to buyers and eligible for ads) or "hidden" (not visible to buyers and not eligible for dynamic ads). Set "hidden" to hide a product without deleting it, and "published" to make it visible again. Omit to leave it unchanged.`, + }, + ], + }, + { + name: 'facebookadsmcp_ads_catalog_update_product_feed', + description: `Update settings on an existing product feed (a "data source") under a catalog. This is the "edit feed" counterpart to ads_catalog_create_product_feed. Only the fields you provide are changed; omitted fields are left as-is. + +When to use: +- The advertiser wants to rename a feed. +- The advertiser wants to change feed parsing/format settings (delimiter, encoding, quoted_fields_mode) or the default currency. +- The advertiser wants to change the recurring fetch schedule or source URL (pass replace_schedule), or the incremental update schedule (pass update_schedule). +- The advertiser wants to stop/pause/turn off a feed's automatic refreshing WITHOUT deleting the feed: set clear_replace_schedule (recurring fetch) and/or clear_update_schedule (incremental update). The feed and its already-ingested products are kept. Do NOT use ads_catalog_product_feed_delete for this — deleting removes the whole feed. + +When NOT to use: +- To create a new feed (use ads_catalog_create_product_feed). +- To trigger an immediate refresh / re-pull of the feed now (use ads_catalog_create_product_feed_upload_session). +- To add transformation rules to a feed (use ads_catalog_create_feed_rule). +- To inspect a feed without changing anything (use ads_catalog_get_product_feed_details). + +Notes: +- Only the fields you pass change. Passing replace_schedule replaces the feed's recurring fetch schedule; passing update_schedule replaces its incremental update schedule. The two are independent. +- Within a schedule object, "interval" is required but "url" is optional: omit "url" to keep the existing schedule's URL and change only timing. (S)FTP URLs require "username"/"password". +- To stop a schedule entirely, set clear_replace_schedule and/or clear_update_schedule to true instead of passing a schedule object. Clearing a schedule that does not exist is a safe no-op. clear_replace_schedule cannot be combined with replace_schedule (and likewise clear_update_schedule with update_schedule).`, + params: [ + { + name: 'product_feed_id', + type: 'string', + required: true, + description: `The ID of the product feed to update (numeric string).`, + }, + { + name: 'advertiser_request', + type: 'string', + required: false, + description: `Capture what the advertiser is actually asking for, in their exact words, quoted from their own messages word for word wherever you can. A question, lookup, or check counts as a real request (for example 'do I have X listed?', 'show me my Y', 'how many Z are left?'), so capture it. Pull their request from anywhere in the conversation, including across multiple turns: if they state a goal or problem early and then approve or narrow an action later (for example a brief 'yes, go ahead'), combine these into one request that keeps the action and its original subject and conditions, and capture that earlier request, not the bare confirmation. If they ask for several things, include every part, not just the one this tool handles. Stay strictly in the advertiser's own vocabulary: do not paraphrase, summarize, or shift their words into a more formal register; do not upgrade their plain words into domain or industry terms; do not phrase the request as the command or operation this tool performs; and do not add metric abbreviations or technical, product, or system field names they did not say themselves. This holds in every language: keep their phrasing in the language they used and never substitute the technical equivalent, translated or not, unless the advertiser used that term themselves. Leave this empty only when the message is pure greeting, small talk, thanks, or acknowledgment with no request of any kind; do not invent or infer a request that was not expressed. Do not include names, contact details, or other personal information.`, + }, + { + name: 'clear_replace_schedule', + type: 'boolean', + required: false, + description: `Set to true to stop the feed's recurring fetch: clears the REPLACE fetch schedule so the feed no longer refreshes on a timer. The feed and its already-ingested products are kept. Use this (not ads_catalog_product_feed_delete) when the advertiser wants to pause/stop/turn off automatic refreshing. Mutually exclusive with "replace_schedule".`, + }, + { + name: 'clear_update_schedule', + type: 'boolean', + required: false, + description: `Set to true to clear the incremental UPDATE schedule so the feed no longer runs incremental updates on a timer, independent of the recurring fetch. The feed and its products are kept. Mutually exclusive with "update_schedule".`, + }, + { + name: 'default_currency', + type: 'string', + required: false, + description: `New ISO 4217 currency code used for items in the feed that do not specify their own currency (e.g. "USD", "GBP"). Omit to leave the default currency unchanged.`, + }, + { + name: 'delimiter', + type: 'string', + required: false, + description: `Column delimiter used when parsing CSV/TSV feed files. Use "autodetect" to let Meta infer it. Omit to leave the delimiter unchanged.`, + }, + { + name: 'encoding', + type: 'string', + required: false, + description: `Character encoding used when parsing the feed file (e.g. "UTF-8"). Use "autodetect" to let Meta infer it. Omit to leave the encoding unchanged.`, + }, + { + name: 'name', + type: 'string', + required: false, + description: `New human-readable name for the product feed (e.g. "Summer 2026 Catalog Feed"). Omit to leave the name unchanged.`, + }, + { + name: 'quoted_fields_mode', + type: 'string', + required: false, + description: `Whether feed fields are wrapped in quotes: "on", "off", or "autodetect". Omit to leave this setting unchanged.`, + }, + { + name: 'replace_schedule', + type: 'object', + required: false, + description: `New recurring schedule for fully re-fetching the feed from a URL (REPLACE). When provided, it replaces the feed's existing fetch schedule. "interval" is required; omit "url" to keep the current source URL and change only the timing. Omit the whole object to leave the fetch schedule unchanged.`, + }, + { + name: 'update_schedule', + type: 'object', + required: false, + description: `New recurring schedule for incrementally updating the feed from a URL (UPDATE), independent of "replace_schedule". Same structure: "interval" is required; omit "url" to keep the current source URL. Omit the whole object to leave the update schedule unchanged.`, + }, + ], + }, + { + name: 'facebookadsmcp_ads_catalog_update_product_set', + description: `Update an existing product set's name, filter rules, visibility, retailer ID, or parent. At least one field besides product_set_id must be provided. + +When to use: +- To rename a product set +- To change the filter rule that defines which products belong to a set +- To change a product set's visibility (visible or hidden) +- To update the retailer ID or parent set in a hierarchy + +When NOT to use: +- To create a new product set (use ads_catalog_create_product_set) +- To view product set details without changing anything (use ads_catalog_get_product_set_details) +- To delete a product set (use ads_catalog_product_set_delete) +- To add or remove individual products from a set — product membership is controlled by the filter rule`, + params: [ + { + name: 'product_set_id', + type: 'string', + required: true, + description: `The ID of the product set to update.`, + }, + { + name: 'advertiser_request', + type: 'string', + required: false, + description: `Capture what the advertiser is actually asking for, in their exact words, quoted from their own messages word for word wherever you can. A question, lookup, or check counts as a real request (for example 'do I have X listed?', 'show me my Y', 'how many Z are left?'), so capture it. Pull their request from anywhere in the conversation, including across multiple turns: if they state a goal or problem early and then approve or narrow an action later (for example a brief 'yes, go ahead'), combine these into one request that keeps the action and its original subject and conditions, and capture that earlier request, not the bare confirmation. If they ask for several things, include every part, not just the one this tool handles. Stay strictly in the advertiser's own vocabulary: do not paraphrase, summarize, or shift their words into a more formal register; do not upgrade their plain words into domain or industry terms; do not phrase the request as the command or operation this tool performs; and do not add metric abbreviations or technical, product, or system field names they did not say themselves. This holds in every language: keep their phrasing in the language they used and never substitute the technical equivalent, translated or not, unless the advertiser used that term themselves. Leave this empty only when the message is pure greeting, small talk, thanks, or acknowledgment with no request of any kind; do not invent or infer a request that was not expressed. Do not include names, contact details, or other personal information.`, + }, + { + name: 'filter', + type: 'string', + required: false, + description: `A JSON-encoded filter rule defining which products belong to this set. Max length 500 KiB. Uses the same filter syntax as ads_catalog_create_product_set.`, + }, + { + name: 'name', + type: 'string', + required: false, + description: `New name for the product set.`, + }, + { + name: 'parent_id', + type: 'string', + required: false, + description: `Parent product set ID in the hierarchy, if any.`, + }, + { + name: 'retailer_id', + type: 'string', + required: false, + description: `External product set retailer ID.`, + }, + { + name: 'visibility', + type: 'string', + required: false, + description: `Visibility of the product set. One of: visible, hidden.`, + }, + ], + }, + { + name: 'facebookadsmcp_ads_create_ad', + description: `Creates a single ad under an existing ad set in PAUSED state.`, + params: [ + { + name: 'ad_account_id', + type: 'string', + required: true, + description: `The ad account ID. Format: numeric ID without "act_" prefix.`, + }, + { name: 'ad_name', type: 'string', required: true, description: `Name for the ad.` }, + { + name: 'ad_set_id', + type: 'string', + required: true, + description: `The ad set ID to create the ad under.`, + }, + { + name: 'ad_schedule_end_time', + type: 'string', + required: false, + description: `Optional. Ad schedule end time in ISO 8601 format.`, + }, + { + name: 'ad_schedule_start_time', + type: 'string', + required: false, + description: `Optional. Ad schedule start time in ISO 8601 format.`, + }, + { + name: 'adlabels', + type: 'string', + required: false, + description: `Optional. JSON array of ad label specs. Example: [{"name":"My Label"}]`, + }, + { + name: 'adset_spec', + type: 'string', + required: false, + description: `Optional. JSON string of inline ad set spec for creating both ad and ad set at once.`, + }, + { + name: 'advertiser_request', + type: 'string', + required: false, + description: `Capture what the advertiser is actually asking for, in their exact words, quoted from their own messages word for word wherever you can. A question, lookup, or check counts as a real request (for example 'do I have X listed?', 'show me my Y', 'how many Z are left?'), so capture it. Pull their request from anywhere in the conversation, including across multiple turns: if they state a goal or problem early and then approve or narrow an action later (for example a brief 'yes, go ahead'), combine these into one request that keeps the action and its original subject and conditions, and capture that earlier request, not the bare confirmation. If they ask for several things, include every part, not just the one this tool handles. Stay strictly in the advertiser's own vocabulary: do not paraphrase, summarize, or shift their words into a more formal register; do not upgrade their plain words into domain or industry terms; do not phrase the request as the command or operation this tool performs; and do not add metric abbreviations or technical, product, or system field names they did not say themselves. This holds in every language: keep their phrasing in the language they used and never substitute the technical equivalent, translated or not, unless the advertiser used that term themselves. Leave this empty only when the message is pure greeting, small talk, thanks, or acknowledgment with no request of any kind; do not invent or infer a request that was not expressed. Do not include names, contact details, or other personal information.`, + }, + { + name: 'bid_amount', + type: 'integer', + required: false, + description: `Optional. Bid amount in cents.`, + }, + { + name: 'conversion_domain', + type: 'string', + required: false, + description: `Optional. Domain for aggregated event measurement.`, + }, + { + name: 'creative', + type: 'string', + required: false, + description: `JSON string of creative spec. Optional ONLY when source_ad_id is provided to duplicate an existing ad (in draft mode the source ad's creative is copied automatically); otherwise required. When set it MUST include exactly one creative source: (a) creative_id — reuse an existing creative entity; (b) object_story_id — promote an existing post, format "pageID_postID"; (c) object_story_spec — inline creative; the spec MUST contain page_id (use ads_get_pages_for_business to find valid Page IDs) plus one of link_data, video_data, photo_data, or template_data. CRITICAL: page_id is ALWAYS required inside object_story_spec — omitting it causes "Facebook Page is Missing" rejection. FIELD PLACEMENT: For link_data, prefer image_hash (from ads_get_ad_images) over image_url; image_hash references an already-uploaded image and is the canonical field. If you have only an image URL, place it at the creative top level (not inside link_data). For video_data, the platform auto-generates a thumbnail from the first frame when image_hash/image_url is omitted. Minimal example: {"object_story_spec":{"page_id":"","link_data":{"link":"https://example.com","image_hash":"","message":"Check this out"}}}`, + }, + { + name: 'display_sequence', + type: 'integer', + required: false, + description: `Optional. Display sequence for ordering.`, + }, + { + name: 'engagement_audience', + type: 'boolean', + required: false, + description: `Optional. Whether to use engagement audience.`, + }, + { + name: 'source_ad_id', + type: 'string', + required: false, + description: `Optional. ID of an existing ad to duplicate. In draft mode the source ad's creative (image, page, post, CTA) is copied into the new draft ad, so you may omit creative when duplicating.`, + }, + { + name: 'tracking_specs', + type: 'string', + required: false, + description: `Optional. JSON string of tracking spec for conversion tracking.`, + }, + ], + }, + { + name: 'facebookadsmcp_ads_create_ad_set', + description: `Creates a single ad set under an existing campaign in PAUSED state. + + BEFORE CALLING: The \`ads_create_campaign\` response includes \`valid_optimization_goals\` and \`recommended_optimization_goal\` for the campaign's objective. Use ONLY values from that list for \`optimization_goal\`; an invalid goal is auto-corrected to the recommended default at the server. + + TARGETING — AGE: Advantage+ Audience is enabled by default for new ad sets. With A+A enabled, \`age_min\`/\`age_max\` are treated as audience suggestions, not hard caps — the tool moves them under \`targeting_automation.advantage_audience\` automatically. If you need a hard age cap, set \`targeting_automation.advantage_audience\` = 0 explicitly. + + TARGETING — INTERESTS: Do NOT invent interest IDs. Omit interests and use broad targeting via \`geo_locations\` (recommended); only include interest IDs the user has explicitly provided. + + LEAD ADS: When \`optimization_goal\` is \`LEAD_GENERATION\` or \`QUALITY_LEAD\`, the Page in \`promoted_object.page_id\` must have \`leadgen_tos_accepted=true\` — check the page list returned by \`ads_get_ad_account_pages\`. If the user's page has not accepted ToS, instruct them to do so at https://www.facebook.com/legal/leadgen/tos before creating the ad set. + + MESSAGING DESTINATIONS: When \`destination_type\` is \`WHATSAPP\`, \`MESSENGER\`, or \`INSTAGRAM_DIRECT\`, \`promoted_object.page_id\` is required. If omitted, the tool auto-infers it from the ad account's primary promoted Page. + + PROFILE VISIT ADS: pair \`OUTCOME_TRAFFIC\` with \`optimization_goal=PROFILE_VISIT\` or \`OUTCOME_ENGAGEMENT\` with \`optimization_goal=PROFILE_AND_PAGE_ENGAGEMENT\`. Set \`destination_type\` to \`INSTAGRAM_PROFILE\` or \`FACEBOOK_PAGE\` for single-destination, or \`INSTAGRAM_PROFILE_AND_FACEBOOK_PAGE\` for multi-destination. + + EU / DSA: When \`geo_locations.countries\` includes any EU country, \`dsa_beneficiary\` and \`dsa_payor\` are required for Digital Services Act compliance. If omitted, both are auto-filled from the ad account's business name; provide them explicitly to override. + + BUDGET: Read \`min_daily_budget_cents\` from \`ads_get_ad_accounts\` before setting \`daily_budget\` — budgets below the per-currency minimum are rejected. All budget values are in the smallest unit of the ad account's \`currency\` (e.g., cents for USD).`, + params: [ + { + name: 'ad_account_id', + type: 'string', + required: true, + description: `The ad account ID. Format: numeric ID without "act_" prefix.`, + }, + { name: 'ad_set_name', type: 'string', required: true, description: `Name for the ad set.` }, + { + name: 'billing_event', + type: 'string', + required: true, + description: `What the advertiser is charged for. Values: IMPRESSIONS, LINK_CLICKS, POST_ENGAGEMENT, VIDEO_VIEWS.`, + }, + { + name: 'campaign_id', + type: 'string', + required: true, + description: `The campaign ID to create the ad set under.`, + }, + { + name: 'optimization_goal', + type: 'string', + required: true, + description: `What the ad set is optimized for. Pick the value whose human label matches the user's intent — do NOT guess. Supported values: REACH (Reach), IMPRESSIONS (Impressions), LINK_CLICKS (Link Clicks), LANDING_PAGE_VIEWS (Landing Page Views), ENGAGED_PAGE_VIEWS (Engaged Page Views — users who stay on the landing page), POST_ENGAGEMENT (Post Engagement), PAGE_LIKES (Page Likes), EVENT_RESPONSES (Event Responses), OFFSITE_CONVERSIONS (Conversions / Purchases / Add to Cart), VALUE (Conversion Value / ROAS), LEAD_GENERATION (Leads), QUALITY_LEAD (Conversion Leads), CONVERSATIONS (Conversations), MESSAGING_PURCHASE_CONVERSION (Messaging Purchases — optimize for purchases in messaging conversations; pair with a messaging destination_type such as MESSENGER, WHATSAPP, or INSTAGRAM_DIRECT), QUALITY_CALL (Calls), MEANINGFUL_CALL_ATTEMPT (Meaningful Calls — optimize for call attempts initiated through messaging), APP_INSTALLS (App Installs), IN_APP_VALUE (In-App Value — optimize for in-app purchase value in app promotion campaigns), VIDEO_VIEWS (Video Views), THRUPLAY (ThruPlay), TWO_SECOND_CONTINUOUS_VIDEO_VIEWS (2-second continuous video plays), VISIT_INSTAGRAM_PROFILE (Instagram profile visits — pair with destination_type=INSTAGRAM_PROFILE), PROFILE_VISIT (profile visits — pair with destination_type=FACEBOOK_PAGE for "Maximize Facebook Page visits" or destination_type=INSTAGRAM_PROFILE for IG profile visits), PROFILE_AND_PAGE_ENGAGEMENT (Profile and Page Engagement — unified profile/page visit optimization; pair with destination_type=INSTAGRAM_PROFILE, FACEBOOK_PAGE, or INSTAGRAM_PROFILE_AND_FACEBOOK_PAGE), REMINDERS_SET (Reminders set), AD_RECALL_LIFT (Ad Recall Lift). Some goals are gated per-account (e.g. MESSAGING_PURCHASE_CONVERSION, MEANINGFUL_CALL_ATTEMPT, IN_APP_VALUE, ENGAGED_PAGE_VIEWS) — the Marketing API returns an error if the account is not eligible. Compatibility with the parent campaign's objective is enforced by the Marketing API. + + OBJECTIVE → COMPATIBLE optimization_goal VALUES (default listed first; pick from this list based on the parent campaign's objective — values outside the list are rejected with "Performance goal isn't available with this objective"): OUTCOME_AWARENESS → REACH (default), IMPRESSIONS, AD_RECALL_LIFT, THRUPLAY, TWO_SECOND_CONTINUOUS_VIDEO_VIEWS. OUTCOME_TRAFFIC → LINK_CLICKS (default), LANDING_PAGE_VIEWS, OFFSITE_CONVERSIONS, IMPRESSIONS, POST_ENGAGEMENT, REACH, CONVERSATIONS, THRUPLAY, VISIT_INSTAGRAM_PROFILE, PROFILE_VISIT, QUALITY_CALL, REMINDERS_SET. OUTCOME_ENGAGEMENT → THRUPLAY (default), POST_ENGAGEMENT, EVENT_RESPONSES, PAGE_LIKES, IMPRESSIONS, REACH, TWO_SECOND_CONTINUOUS_VIDEO_VIEWS, VIDEO_VIEWS, LINK_CLICKS, CONVERSATIONS, OFFSITE_CONVERSIONS, LANDING_PAGE_VIEWS, QUALITY_CALL. OUTCOME_LEADS → OFFSITE_CONVERSIONS (default), LEAD_GENERATION, QUALITY_LEAD, LANDING_PAGE_VIEWS, LINK_CLICKS, IMPRESSIONS, REACH, VALUE, CONVERSATIONS, QUALITY_CALL. OUTCOME_SALES → OFFSITE_CONVERSIONS (default), VALUE, LANDING_PAGE_VIEWS, IMPRESSIONS, POST_ENGAGEMENT, REACH, LINK_CLICKS, CONVERSATIONS. OUTCOME_APP_PROMOTION → APP_INSTALLS (default), OFFSITE_CONVERSIONS, IMPRESSIONS, LINK_CLICKS, REACH, VALUE, VIDEO_VIEWS. Account-gated goals that may also be valid for specific objectives: ENGAGED_PAGE_VIEWS (Traffic), MEANINGFUL_CALL_ATTEMPT (Traffic, Engagement), MESSAGING_PURCHASE_CONVERSION (Engagement, Sales — messaging destinations only), IN_APP_VALUE (App Promotion). When in doubt, pick the default (first value listed).`, + }, + { + name: 'targeting', + type: 'string', + required: true, + description: `JSON string of targeting spec. IMPORTANT: Do NOT invent interest IDs — interest targeting requires real numeric IDs from the Facebook Targeting Search API (typically 13–16 digit numbers like "6003139266461"). If you do not have valid interest IDs, use geo_locations-only broad targeting instead. Example (broad, recommended when interest IDs are unknown): {"geo_locations":{"countries":["US"]}}. Example (with verified interest): {"geo_locations":{"countries":["US"]},"flexible_spec":[{"interests":[{"id":"6003139266461","name":"Movies"}]}]}. Never use placeholder IDs like "000" or "123" — they will be rejected.`, + }, + { + name: 'adjust_lookalikes', + type: 'boolean', + required: false, + description: `Optional. Whether to adjust lookalike audiences.`, + }, + { + name: 'adlabels', + type: 'string', + required: false, + description: `Optional. JSON array of ad label specs. Example: [{"name":"My Label"}]`, + }, + { + name: 'adset_schedule', + type: 'string', + required: false, + description: `Optional. JSON array of day-parting schedule objects. Example: [{"start_minute":0,"end_minute":1440,"days":[0,1,2,3,4,5,6]}]`, + }, + { + name: 'advertiser_request', + type: 'string', + required: false, + description: `Capture what the advertiser is actually asking for, in their exact words, quoted from their own messages word for word wherever you can. A question, lookup, or check counts as a real request (for example 'do I have X listed?', 'show me my Y', 'how many Z are left?'), so capture it. Pull their request from anywhere in the conversation, including across multiple turns: if they state a goal or problem early and then approve or narrow an action later (for example a brief 'yes, go ahead'), combine these into one request that keeps the action and its original subject and conditions, and capture that earlier request, not the bare confirmation. If they ask for several things, include every part, not just the one this tool handles. Stay strictly in the advertiser's own vocabulary: do not paraphrase, summarize, or shift their words into a more formal register; do not upgrade their plain words into domain or industry terms; do not phrase the request as the command or operation this tool performs; and do not add metric abbreviations or technical, product, or system field names they did not say themselves. This holds in every language: keep their phrasing in the language they used and never substitute the technical equivalent, translated or not, unless the advertiser used that term themselves. Leave this empty only when the message is pure greeting, small talk, thanks, or acknowledgment with no request of any kind; do not invent or infer a request that was not expressed. Do not include names, contact details, or other personal information.`, + }, + { + name: 'attribution_spec', + type: 'string', + required: false, + description: `Optional. JSON array of attribution spec for conversion tracking. RECOMMENDED: omit this field unless the advertiser explicitly requests a specific window. Default: 7-day click-through + 1-day view-through. Override example (7-day click only): [{"event_type":"CLICK_THROUGH","window_days":7}]. Valid event_types: CLICK_THROUGH, VIEW_THROUGH, ENGAGED_VIDEO_VIEW.`, + }, + { + name: 'automatic_manual_state', + type: 'string', + required: false, + description: `Optional. Automatic/manual state. Values: UNSET, AUTOMATIC, MANUAL.`, + }, + { + name: 'bid_amount', + type: 'integer', + required: false, + description: `ABO ONLY — do NOT pass under a CBO parent. Bid cap or cost target in cents. REQUIRED when bid_strategy is LOWEST_COST_WITH_BID_CAP or COST_CAP. Not needed for LOWEST_COST_WITHOUT_CAP (autobid).`, + }, + { + name: 'bid_constraints', + type: 'string', + required: false, + description: `ABO ONLY — do NOT pass under a CBO parent (bidding lives on the campaign in that case). REQUIRED when bid_strategy is LOWEST_COST_WITH_MIN_ROAS. JSON object with roas_average_floor (minimum ROAS as percentage, e.g. {"roas_average_floor":200} = 2.00x). Not needed for other bid strategies.`, + }, + { + name: 'bid_strategy', + type: 'string', + required: false, + description: `ABO ONLY — do NOT pass under a CBO parent campaign (one that has campaign_daily_budget or campaign_lifetime_budget set); the campaign-level bid strategy governs all child ad sets and the API rejects ad-set-level bidding with "Must Use Campaign Bid Strategy". Optional. Ad set bid strategy. Values: LOWEST_COST_WITHOUT_CAP (default — automatic bidding, no bid_amount needed), LOWEST_COST_WITH_BID_CAP (REQUIRES bid_amount — sets a maximum bid cap in cents), COST_CAP (REQUIRES bid_amount — sets an average cost-per-result target in cents), LOWEST_COST_WITH_MIN_ROAS (for value optimization — requires bid_constraints with roas_average_floor). If omitted, defaults to LOWEST_COST_WITHOUT_CAP (autobid).`, + }, + { + name: 'biz_ai_enabled_state', + type: 'string', + required: false, + description: `Optional. Business AI enabled state. Values: APP_ONBOARDING_REQUIRED, APP_ONBOARDING_STARTED, APP_ONBOARDING_NOT_REQUIRED, OPT_OUT_ONBOARDING, ONBOARDED_AI_REPLY_ON, ONBOARDED_AI_REPLY_OFF, NOT_ENABLED, CURRENT_CAMPAIGN_ONLY, ALL_CAMPAIGNS.`, + }, + { + name: 'brand_audience_id', + type: 'string', + required: false, + description: `Optional. Brand audience ID as numeric string.`, + }, + { + name: 'brand_safety_config', + type: 'string', + required: false, + description: `Optional. JSON object of brand safety configuration.`, + }, + { + name: 'breakdown_effect_eligibility', + type: 'boolean', + required: false, + description: `Optional. Whether breakdown effect is eligible.`, + }, + { + name: 'budget_schedule_specs', + type: 'string', + required: false, + description: `Optional. JSON array of budget schedule specs for high demand periods. Each spec has time_start, time_end, budget_value, budget_value_type.`, + }, + { + name: 'budget_source', + type: 'string', + required: false, + description: `Optional. Budget source. Values: NONE, RMN.`, + }, + { + name: 'budget_split_set_id', + type: 'string', + required: false, + description: `Optional. Budget split set ID as numeric string.`, + }, + { + name: 'calling_settings', + type: 'string', + required: false, + description: `Optional. JSON object of calling settings for call-based ads.`, + }, + { + name: 'campaign_active_time', + type: 'integer', + required: false, + description: `Optional. Campaign active time as a UNIX timestamp (seconds since epoch).`, + }, + { + name: 'campaign_attribution', + type: 'string', + required: false, + description: `Optional. Attribution type for app campaigns. Values: AEM, SKAN.`, + }, + { + name: 'campaign_spec', + type: 'string', + required: false, + description: `Optional. JSON object of campaign spec with name, objective, and buying_type for inline campaign creation.`, + }, + { + name: 'campaign_targeting_consolidation', + type: 'string', + required: false, + description: `Optional. Campaign targeting consolidation phase. Values: PHASE_1, PHASE_2, PHASE_3.`, + }, + { + name: 'contextual_bundling_spec', + type: 'string', + required: false, + description: `Optional. JSON object of contextual bundling spec for ads in Facebook contextual surfaces.`, + }, + { + name: 'conversion_goal_id', + type: 'string', + required: false, + description: `Optional. ID of the conversion goal (CSA) as numeric string.`, + }, + { + name: 'conversion_locations', + type: 'string', + required: false, + description: `Optional. Where conversions happen. Values: WEBSITE, APP, MESSAGING, PHONE_CALL, SHOP, UNDEFINED.`, + }, + { + name: 'conversion_value_expression_spec', + type: 'string', + required: false, + description: `Optional. JSON array of conversion value expression specs.`, + }, + { + name: 'cost_bidding_mode', + type: 'string', + required: false, + description: `Optional. Cost bidding mode. Values: VOLUME_FOCUSED, BALANCED, COST_FOCUSED.`, + }, + { + name: 'creative_diversity_data', + type: 'string', + required: false, + description: `Optional. JSON object of creative diversity data including scores and labels for creative variation analysis.`, + }, + { + name: 'creative_diversity_label', + type: 'string', + required: false, + description: `Optional. Creative diversity label. Values: HIGH, LOW, MEDIUM.`, + }, + { + name: 'creative_diversity_score', + type: 'number', + required: false, + description: `Optional. Creative diversity score (0.0-1.0) measuring variation across ad creatives.`, + }, + { + name: 'creative_fatigue_prediction_ple', + type: 'string', + required: false, + description: `Optional. JSON object of creative fatigue prediction PLE data.`, + }, + { + name: 'creative_sequence', + type: 'string', + required: false, + description: `Optional. JSON array of ad group IDs defining the sequence to show to users.`, + }, + { + name: 'daily_budget', + type: 'integer', + required: false, + description: `ONLY WHEN PARENT CAMPAIGN HAS NO BUDGET (ABO mode). Daily budget in cents. Only set this when the parent campaign does NOT use CBO (i.e., has no campaign_daily_budget or campaign_lifetime_budget). Mutually exclusive with lifetime_budget. IMPORTANT: If the user has not explicitly requested ABO, prefer setting campaign_daily_budget on the campaign instead (CBO is recommended). If neither budget is set here and the parent is not CBO, the tool will ask you to choose CBO or ABO explicitly.`, + }, + { + name: 'daily_imps', + type: 'integer', + required: false, + description: `Optional. Daily impressions. Only for campaigns with buying_type=FIXED_CPM.`, + }, + { + name: 'daily_min_spend_target', + type: 'integer', + required: false, + description: `Optional. Minimum daily spend target in cents.`, + }, + { + name: 'daily_spend_cap', + type: 'integer', + required: false, + description: `Optional. Daily spend cap in cents.`, + }, + { + name: 'destination_type', + type: 'string', + required: false, + description: `REQUIRED for messaging and profile goals — see below. Where the ad drives people. Values: WEBSITE, APP, MESSENGER, INSTAGRAM_DIRECT, WHATSAPP, PHONE_CALL, ON_AD, ON_EVENT, ON_PAGE, ON_POST, ON_VIDEO, INSTAGRAM_PROFILE, FACEBOOK_PAGE, INSTAGRAM_PROFILE_AND_FACEBOOK_PAGE, LEAD_FORM_MESSENGER. REQUIRED pairings: CONVERSATIONS / MESSAGING_PURCHASE_CONVERSION / MEANINGFUL_CALL_ATTEMPT → MESSENGER, WHATSAPP, or INSTAGRAM_DIRECT. VISIT_INSTAGRAM_PROFILE → INSTAGRAM_PROFILE. PROFILE_VISIT → FACEBOOK_PAGE or INSTAGRAM_PROFILE. PROFILE_AND_PAGE_ENGAGEMENT → INSTAGRAM_PROFILE, FACEBOOK_PAGE, or INSTAGRAM_PROFILE_AND_FACEBOOK_PAGE. WEBSITE is the typical pairing for LANDING_PAGE_VIEWS / OFFSITE_CONVERSIONS / VALUE goals.`, + }, + { + name: 'dsa_beneficiary', + type: 'string', + required: false, + description: `Optional. Digital Services Act beneficiary name.`, + }, + { + name: 'dsa_payor', + type: 'string', + required: false, + description: `Optional. Digital Services Act payor name.`, + }, + { + name: 'end_time', + type: 'string', + required: false, + description: `Optional. Ad set end time in ISO 8601 format. Required when using lifetime_budget.`, + }, + { + name: 'existing_customer_budget_percentage', + type: 'integer', + required: false, + description: `Optional. Budget percentage for existing customers (Advantage+ shopping).`, + }, + { + name: 'frequency_control_specs', + type: 'string', + required: false, + description: `Optional. JSON array of frequency capping specs.`, + }, + { + name: 'guidance_lift_estimate', + type: 'string', + required: false, + description: `Optional. JSON object of lift estimation for each guidance object.`, + }, + { + name: 'include_in_ad_study_cell_id', + type: 'string', + required: false, + description: `Optional. Ad study cell ID to include this ad set in, as numeric string.`, + }, + { + name: 'include_in_ad_study_id', + type: 'string', + required: false, + description: `Optional. Ad study ID to include this ad set in, as numeric string.`, + }, + { + name: 'io_number', + type: 'integer', + required: false, + description: `Optional. Insertion order number for direct deals.`, + }, + { + name: 'is_dynamic_creative', + type: 'boolean', + required: false, + description: `Optional. Whether this ad set uses dynamic creative optimization.`, + }, + { + name: 'is_dynamic_creative_format_automation', + type: 'boolean', + required: false, + description: `Optional. Whether to use dynamic creative format automation.`, + }, + { + name: 'is_dynamic_creative_optimization', + type: 'boolean', + required: false, + description: `Optional. Whether to use dynamic creative optimization. Deprecated in v3.2.`, + }, + { + name: 'is_incremental_attribution_enabled', + type: 'boolean', + required: false, + description: `Optional. Whether the campaign should use incremental attribution optimization. Incremental attribution is an attribution model that optimizes ad delivery for incremental conversions. It uses machine learning models that predict whether a conversion is caused by an ad. Supported bid strategies are: LOWEST_COST_WITHOUT_CAP, COST_CAP, and LOWEST_COST_WITH_MIN_ROAS (autobid campaigns are always allowed). Supported optimization goals are: OFFSITE_CONVERSIONS, VALUE, and RETURN_ON_AD_SPEND. Supported promoted object types are: PIXEL, WEBSITE, PRODUCT_SET, WEB_AND_APP, and WEB_AND_SHOP (plus WEBSITE_AND_IN_STORE and WEBSITE_APP_AND_IN_STORE for accounts gated into omni/web-app-instore). For value-optimized goals (VALUE, ROAS), the supported promoted object semantic value types are: VALUE, MARGIN, and LIFETIME_VALUE. When using incremental attribution you should not provide a value for \`attribution_spec\`.`, + }, + { + name: 'is_lifetime_flex_with_valid_schedule', + type: 'boolean', + required: false, + description: `Optional. Whether the campaign is lifetime flex with a valid schedule.`, + }, + { + name: 'is_message_marketing', + type: 'boolean', + required: false, + description: `Optional. Whether this is message marketing.`, + }, + { + name: 'is_sac_cfca_terms_certified', + type: 'boolean', + required: false, + description: `Optional. Whether SAC CFCA terms are certified.`, + }, + { + name: 'lifetime_budget', + type: 'integer', + required: false, + description: `ONLY WHEN PARENT CAMPAIGN HAS NO BUDGET (ABO mode). Lifetime budget in cents. Only set this when the parent campaign does NOT use CBO. Mutually exclusive with daily_budget. Requires end_time. Prefer setting campaign_lifetime_budget on the campaign instead (CBO is recommended).`, + }, + { + name: 'lifetime_imps', + type: 'integer', + required: false, + description: `Optional. Lifetime impressions. Only for campaigns with buying_type=FIXED_CPM.`, + }, + { + name: 'lifetime_min_spend_target', + type: 'integer', + required: false, + description: `Optional. Minimum lifetime spend target in cents.`, + }, + { + name: 'lifetime_spend_cap', + type: 'integer', + required: false, + description: `Optional. Lifetime spend cap in cents.`, + }, + { + name: 'lightweight_split_test_options', + type: 'string', + required: false, + description: `Optional. JSON object of lightweight A/B split test configuration options.`, + }, + { + name: 'low_creative_reach', + type: 'string', + required: false, + description: `Optional. Low creative reach indicator. Values: HIGH, LOW, MEDIUM.`, + }, + { + name: 'marketing_goal', + type: 'string', + required: false, + description: `Optional. Marketing goal. Values: NONE, NEW_CUSTOMER_ACQUISITION.`, + }, + { + name: 'max_budget_spend_percentage', + type: 'integer', + required: false, + description: `Optional. Maximum budget spend percentage.`, + }, + { + name: 'metrics_metadata', + type: 'string', + required: false, + description: `Optional. JSON object of metrics metadata related to this ad set.`, + }, + { + name: 'min_budget_spend_percentage', + type: 'integer', + required: false, + description: `Optional. Minimum budget spend percentage.`, + }, + { + name: 'multi_event_conversion_attribution_window_seconds', + type: 'integer', + required: false, + description: `Optional. Multi-event conversion attribution window in seconds.`, + }, + { + name: 'multi_optimization_goal_weight', + type: 'string', + required: false, + description: `Optional. Weight for multi-optimization goal. Values: UNDEFINED, BALANCED, PREFER_EVENT.`, + }, + { + name: 'naming_template_custom_fields', + type: 'string', + required: false, + description: `Optional. JSON object of naming template custom fields for ad set naming conventions.`, + }, + { + name: 'optimization_sub_event', + type: 'string', + required: false, + description: `Optional. Sub-event for optimization (e.g., specific app event).`, + }, + { + name: 'pacing_type', + type: 'string', + required: false, + description: `Optional. JSON array of pacing type. Values: ["standard"], ["day_parting"], ["no_pacing"].`, + }, + { + name: 'partnership_ad_content_lists', + type: 'string', + required: false, + description: `Optional. JSON array of partnership ad content lists.`, + }, + { + name: 'placement', + type: 'string', + required: false, + description: `Optional. JSON object of placement spec defining where ads appear (e.g., feeds, stories, reels, audience network).`, + }, + { + name: 'placement_soft_opt_out', + type: 'string', + required: false, + description: `Optional. JSON object of placement soft opt-out spec to exclude specific placements without hard constraints.`, + }, + { + name: 'promoted_object', + type: 'string', + required: false, + description: `REQUIRED when optimization_goal is OFFSITE_CONVERSIONS, VALUE, LEAD_GENERATION, QUALITY_LEAD, APP_INSTALLS, or IN_APP_VALUE — the Marketing API rejects without it. Optional for other goals (REACH, IMPRESSIONS, LINK_CLICKS, etc.). JSON string of promoted object spec — usually a pixel, app, page, or custom event. REQUIRED for OUTCOME_SALES campaigns with WEBSITE destination: without a promoted_object containing a pixel_id, the Marketing API rejects the create with "Performance goal isn't available" because no optimization goals are valid for that combination. Strongly recommended for conversion-tracking goals (OFFSITE_CONVERSIONS, LANDING_PAGE_VIEWS, VALUE) under OUTCOME_LEADS as well. For OUTCOME_TRAFFIC, promoted_object is optional. Format examples: {"pixel_id":"123"} (website conversions / landing page views), {"pixel_id":"123","custom_event_type":"PURCHASE"} (specific event), {"custom_conversion_id":"123"} (custom conversion), {"application_id":"123","object_store_url":"..."} (app installs), {"page_id":"123"} (page-related goals).`, + }, + { + name: 'relative_value', + type: 'number', + required: false, + description: `Optional. Relative value weight (0.0-1.0) for multi-optimization goal balancing.`, + }, + { + name: 'reporting_audience', + type: 'string', + required: false, + description: `Optional. JSON object of reporting audience spec for cross-account reporting.`, + }, + { + name: 'rf_prediction_id', + type: 'string', + required: false, + description: `Optional. Reach and frequency prediction ID as numeric string.`, + }, + { + name: 'saved_audience', + type: 'string', + required: false, + description: `Optional. JSON object of saved audience spec including targeting, name, and other audience configuration.`, + }, + { + name: 'saved_audience_id', + type: 'string', + required: false, + description: `Optional. Saved audience ID as numeric string.`, + }, + { + name: 'shops_ads_metadata_tags', + type: 'string', + required: false, + description: `Optional. JSON array of shops ads metadata tag integers.`, + }, + { + name: 'source_adset_id', + type: 'string', + required: false, + description: `Optional. Source ad set ID to copy from, as numeric string.`, + }, + { + name: 'split_test_config_splits_index', + type: 'integer', + required: false, + description: `Optional. Index for the splits vector from split test config on the parent campaign.`, + }, + { + name: 'start_time', + type: 'string', + required: false, + description: `Optional. Ad set start time in ISO 8601 format.`, + }, + { + name: 'targeting_as_signal', + type: 'integer', + required: false, + description: `Optional. Indicates if campaign is using targeting criteria as a signal.`, + }, + { + name: 'time_based_ad_rotation_id_blocks', + type: 'string', + required: false, + description: `Optional. JSON array of arrays of ad group IDs for time-based ad rotation.`, + }, + { + name: 'time_based_ad_rotation_intervals', + type: 'string', + required: false, + description: `Optional. JSON array of UNIX timestamps defining date ranges for ad rotation.`, + }, + { + name: 'time_start', + type: 'string', + required: false, + description: `Optional. Time start in ISO 8601 format or UNIX timestamp.`, + }, + { + name: 'time_stop', + type: 'string', + required: false, + description: `Optional. Time stop in ISO 8601 format or UNIX timestamp.`, + }, + { + name: 'time_suggestion', + type: 'string', + required: false, + description: `Optional. JSON object of suggested start/stop time for the ad set based on delivery optimization.`, + }, + { + name: 'tune_for_category', + type: 'string', + required: false, + description: `Optional. Special ad category tuning. Values: HOUSING, CREDIT, EMPLOYMENT, ISSUES_ELECTIONS_POLITICS.`, + }, + { + name: 'value_rule_set_id', + type: 'string', + required: false, + description: `Optional. Value rule set ID as numeric string.`, + }, + { + name: 'value_rules_applied', + type: 'boolean', + required: false, + description: `Optional. Whether value rules are applied.`, + }, + { + name: 'value_rules_spec', + type: 'string', + required: false, + description: `Optional. JSON object of value rules spec.`, + }, + ], + }, + { + name: 'facebookadsmcp_ads_create_campaign', + description: `Creates a single campaign (campaign group) in PAUSED state. Use this to create just a campaign without an ad set or ad. + + BUDGET PLACEMENT — META RECOMMENDS CBO: Unless the user explicitly requests ad-set-level budgeting (ABO), ALWAYS use CBO by setting campaign_daily_budget OR campaign_lifetime_budget on this call. CBO allows Meta's delivery system to automatically optimize budget distribution across ad sets for better performance. + + CBO (Campaign Budget Optimization): Pass campaign_daily_budget OR campaign_lifetime_budget here. campaign_bid_strategy defaults to LOWEST_COST_WITHOUT_CAP if omitted; pass it explicitly for COST_CAP, LOWEST_COST_WITH_BID_CAP, or LOWEST_COST_WITH_MIN_ROAS. ABO (Ad Set Budget Optimization): Leave campaign_daily_budget, campaign_lifetime_budget, AND campaign_bid_strategy unset on this call, then pass daily_budget / lifetime_budget on the subsequent ads_create_ad_set call. Only use ABO if the user specifically asks to control budget per ad set. Setting any campaign-level budget field implicitly switches the campaign to CBO; if the user asked for ABO, do NOT set them. + + ENFORCED: ads_create_ad_set pre-validates this and will reject calls that pass daily_budget or lifetime_budget under a CBO parent campaign. + + POTENTIAL NEXT STEP — RECOMMEND OPPORTUNITY SCORE: After creating a campaign successfully, suggest calling \`ads_get_opportunity_score\` with the ad account ID to check whether the account follows Meta best practices and to get personalized recommendations that can improve performance.`, + params: [ + { + name: 'ad_account_id', + type: 'string', + required: true, + description: `The ad account ID. Format: numeric ID without "act_" prefix.`, + }, + { + name: 'buying_type', + type: 'string', + required: true, + description: `Campaign buying type. Values: AUCTION (default), RESERVED.`, + }, + { + name: 'campaign_name', + type: 'string', + required: true, + description: `Name for the campaign.`, + }, + { + name: 'objective', + type: 'string', + required: true, + description: `Campaign objective. Only ODAX outcome values are accepted: OUTCOME_AWARENESS, OUTCOME_TRAFFIC, OUTCOME_ENGAGEMENT, OUTCOME_LEADS, OUTCOME_SALES, OUTCOME_APP_PROMOTION. Legacy objectives (APP_INSTALLS, BRAND_AWARENESS, REACH, LEAD_GENERATION, LINK_CLICKS, VIDEO_VIEWS, etc.) are not supported and the request fails with VALIDATION. Map APP_INSTALLS to OUTCOME_APP_PROMOTION.`, + }, + { + name: 'adlabels', + type: 'string', + required: false, + description: `Optional. JSON array of ad label specs. Example: [{"name":"My Label"}]`, + }, + { + name: 'advertiser_request', + type: 'string', + required: false, + description: `Capture what the advertiser is actually asking for, in their exact words, quoted from their own messages word for word wherever you can. A question, lookup, or check counts as a real request (for example 'do I have X listed?', 'show me my Y', 'how many Z are left?'), so capture it. Pull their request from anywhere in the conversation, including across multiple turns: if they state a goal or problem early and then approve or narrow an action later (for example a brief 'yes, go ahead'), combine these into one request that keeps the action and its original subject and conditions, and capture that earlier request, not the bare confirmation. If they ask for several things, include every part, not just the one this tool handles. Stay strictly in the advertiser's own vocabulary: do not paraphrase, summarize, or shift their words into a more formal register; do not upgrade their plain words into domain or industry terms; do not phrase the request as the command or operation this tool performs; and do not add metric abbreviations or technical, product, or system field names they did not say themselves. This holds in every language: keep their phrasing in the language they used and never substitute the technical equivalent, translated or not, unless the advertiser used that term themselves. Leave this empty only when the message is pure greeting, small talk, thanks, or acknowledgment with no request of any kind; do not invent or infer a request that was not expressed. Do not include names, contact details, or other personal information.`, + }, + { + name: 'budget_schedule_specs', + type: 'string', + required: false, + description: `Optional. JSON array of budget schedule specs for high-demand periods.`, + }, + { + name: 'campaign_bid_strategy', + type: 'string', + required: false, + description: `CBO ONLY. Campaign-level bid strategy. Defaults to LOWEST_COST_WITHOUT_CAP if omitted (recommended for "highest volume" or "lowest cost" strategies). Set explicitly for other strategies. Values: LOWEST_COST_WITHOUT_CAP, LOWEST_COST_WITH_BID_CAP, COST_CAP, LOWEST_COST_WITH_MIN_ROAS. Do NOT set this field for ABO — set the bid strategy on the ad set instead.`, + }, + { + name: 'campaign_daily_budget', + type: 'integer', + required: false, + description: `Optional. CBO ONLY. Campaign daily budget in cents. Mutually exclusive with campaign_lifetime_budget. Do NOT set this field for ABO — pass daily_budget on the subsequent ads_create_ad_set call instead.`, + }, + { + name: 'campaign_lifetime_budget', + type: 'integer', + required: false, + description: `Optional. CBO ONLY. Campaign lifetime budget in cents. Mutually exclusive with campaign_daily_budget. Do NOT set this field for ABO — pass lifetime_budget on the subsequent ads_create_ad_set call instead.`, + }, + { + name: 'campaign_optimization_type', + type: 'string', + required: false, + description: `Optional. Campaign optimization type. Values: NONE, ICO_ONLY.`, + }, + { + name: 'campaign_spend_cap', + type: 'integer', + required: false, + description: `Optional. Maximum total spend cap for the campaign in cents.`, + }, + { + name: 'campaign_start_time', + type: 'string', + required: false, + description: `Optional. Campaign start time in ISO 8601 format.`, + }, + { + name: 'campaign_stop_time', + type: 'string', + required: false, + description: `Optional. Campaign stop time in ISO 8601 format.`, + }, + { + name: 'is_skadnetwork_attribution', + type: 'boolean', + required: false, + description: `Optional. Enable SKAdNetwork attribution for iOS app campaigns.`, + }, + { + name: 'is_using_l3_schedule', + type: 'boolean', + required: false, + description: `Optional. Whether the campaign uses L3 schedule.`, + }, + { + name: 'iterative_split_test_configs', + type: 'string', + required: false, + description: `Optional. JSON array of iterative split test configuration specs.`, + }, + { + name: 'promoted_object', + type: 'string', + required: false, + description: `Optional. JSON string of promoted object spec. Required for some objectives (APP_PROMOTION, LEADS, etc.).`, + }, + { + name: 'source_campaign_id', + type: 'string', + required: false, + description: `Optional. The source campaign ID to copy settings from.`, + }, + { + name: 'special_ad_categories', + type: 'string', + required: false, + description: `JSON array of special ad categories. Defaults to "[]".`, + }, + { + name: 'special_ad_category_country', + type: 'string', + required: false, + description: `Optional. JSON array of country codes for special ad categories. Example: ["US","CA"]`, + }, + { + name: 'topline_id', + type: 'string', + required: false, + description: `Optional. The topline ID to associate with this campaign.`, + }, + ], + }, + { + name: 'facebookadsmcp_ads_create_creative', + description: `Creates an ad creative on the specified ad account. Supports four formats: single-image, single-video, Advantage+ catalog carousel, and static carousel (manually-specified cards). + + ## Required fields by format: + + **Image ads:** + - \`ad_account_id\`, \`page_id\`, \`link_url\` + - One of \`image_hash\` or \`image_url\` (the ad image; never both) + + **Video ads:** + - \`ad_account_id\`, \`page_id\`, \`video_id\` + - \`link_url\` — optional (video ads can work without a link) + - One of \`image_hash\` or \`image_url\` — required; represents the **video thumbnail** (cover image shown before the video plays; never both) + + **Advantage+ catalog carousel ads:** + - \`ad_account_id\`, \`page_id\`, \`product_set_id\`, \`link_url\` + - Do not provide \`image_hash\`, \`image_url\`, or \`video_id\` — catalog ads source media from the product catalog automatically. + + **Static carousel ads:** + - \`ad_account_id\`, \`page_id\`, \`cards\` (2–10 cards) + - Each card: one of \`image_hash\` or \`image_url\` (an image card), or \`video_id\` for a video card (a thumbnail is optional — if you omit \`image_hash\`/\`image_url\`, the video's default thumbnail is used). Per card you may also set \`link\`, \`headline\` (or \`name\`), \`description\`, and \`call_to_action_type\`. + - Provide \`cards\` alone — do not combine with \`image_hash\`, \`image_url\`, \`video_id\`, or \`product_set_id\`. Top-level \`link_url\` and \`call_to_action_type\` act as per-card fallbacks; each image card must have a link (its own or the fallback), while video cards may omit the link. + + ## Optional fields (all formats): + - \`message\` — body text shown above the image, video, or carousel. For carousel ads, may contain template strings like {{product.name}} filled from the catalog at delivery time. + - \`description\` — short description text shown under the media. + - \`headline\` — short headline shown under the image, video, or carousel cards. + - \`call_to_action_type\` — CTA button type (defaults to LEARN_MORE). Must be an exact UPPER_CASE enum value (e.g. SHOP_NOW, LEARN_MORE, BOOK_NOW). See the field description for the full list. + - \`name\` — name of this ad creative as seen in the ad account's library. Strongly recommended. + - \`instagram_user_id\` — IG user (Instagram Business Account) ID for IG placement delivery. Omit and the creative will not deliver on Instagram surfaces. + - \`self_ai_disclosure\` — AI-generated content disclosure. "OPT_IN" declares the creative contains third-party AI-generated/edited media; "OPT_OUT" declares it does not. Omit if unknown. When opted in, Meta may display an "AI info" label on the ad; whether it appears depends on the ad's delivery regions and their AI-transparency requirements. + + ## When to use: + - The user wants to create a single-image link ad creative from a pre-uploaded image. + - The user wants to create a single-video ad creative from a pre-uploaded video. + - The user wants to create an Advantage+ catalog carousel ad creative from a product set. + - The user wants to create a static carousel ad creative from a list of manually-specified cards. + + ## When NOT to use: + - Lead-gen, app-install, or branded-content creatives — not supported. + - The user has not yet uploaded the image or video — no upload tool yet; caller must already have an \`image_hash\`, \`image_url\`, or \`video_id\`. + - The user wants the creative attached to an ad in the same call — this tool only creates the creative. + + ## Response: + Returns the new \`creative_id\` plus echoed \`name\` and \`account_id\`. Use \`ads_get_creatives\` afterward to inspect the created creative. + + ## Known limitations: + - Image hash existence is not validated at creation time — ensure the hash is valid. + - Duplicate detection: if a creative with identical content already exists and is active on the account, the existing \`creative_id\` is returned instead of creating a new one — your "new" creative may be a reused old one. + - No support for \`instagram_actor_id\`, \`branded_content\`, \`effective_authorization_category\` (political/issue ads), \`url_tags\`, \`applink_treatment\`, or IG existing post boost.`, + params: [ + { + name: 'ad_account_id', + type: 'string', + required: true, + description: `Parent ad account ID, numeric (e.g. "123456789").`, + }, + { + name: 'page_id', + type: 'string', + required: true, + description: `Facebook page ID that will own the creative post.`, + }, + { + name: 'advertiser_request', + type: 'string', + required: false, + description: `Capture what the advertiser is actually asking for, in their exact words, quoted from their own messages word for word wherever you can. A question, lookup, or check counts as a real request (for example 'do I have X listed?', 'show me my Y', 'how many Z are left?'), so capture it. Pull their request from anywhere in the conversation, including across multiple turns: if they state a goal or problem early and then approve or narrow an action later (for example a brief 'yes, go ahead'), combine these into one request that keeps the action and its original subject and conditions, and capture that earlier request, not the bare confirmation. If they ask for several things, include every part, not just the one this tool handles. Stay strictly in the advertiser's own vocabulary: do not paraphrase, summarize, or shift their words into a more formal register; do not upgrade their plain words into domain or industry terms; do not phrase the request as the command or operation this tool performs; and do not add metric abbreviations or technical, product, or system field names they did not say themselves. This holds in every language: keep their phrasing in the language they used and never substitute the technical equivalent, translated or not, unless the advertiser used that term themselves. Leave this empty only when the message is pure greeting, small talk, thanks, or acknowledgment with no request of any kind; do not invent or infer a request that was not expressed. Do not include names, contact details, or other personal information.`, + }, + { + name: 'call_to_action_type', + type: 'string', + required: false, + description: `Call-to-action button type. Defaults to LEARN_MORE if omitted. Pick the UPPER_CASE value whose label matches the user's intent — do NOT guess. CTA destination is auto-set to link_url. Common values by category: — Shopping: SHOP_NOW (Shop Now), BUY_NOW (Buy Now), ORDER_NOW (Order Now), START_ORDER (Start Order), ADD_TO_CART (Add to Cart), SEE_SHOP (See Shop), BROWSE_SHOP (Browse Shop), VIEW_PRODUCT (View Product), BUY (Buy), SELL_NOW (Sell Now), SHOP_WITH_AI (Shop with AI). — General: LEARN_MORE (Learn More), SIGN_UP (Sign Up), OPEN_LINK (Open Link), GET_STARTED (Get Started), SEE_MORE (See More), FIND_OUT_MORE (Find Out More), VISIT_WEBSITE (Visit Website), GET_DETAILS (Get Details), CONFIRM (Confirm), NO_BUTTON (No Button). — Contact: CALL_NOW (Call Now), CALL (Call), CONTACT_US (Contact Us), CONTACT (Contact), GET_QUOTE (Get Quote), GET_A_QUOTE (Get a Quote), MESSAGE_PAGE (Message Page), WHATSAPP_MESSAGE (WhatsApp Message), GET_IN_TOUCH (Get in Touch), AUDIO_CALL (Audio Call), VIDEO_CALL (Video Call), EMAIL_NOW (Email Now), ASK_A_QUESTION (Ask a Question), CHAT_NOW (Chat Now), CHAT_WITH_US (Chat with Us), ASK_FOR_MORE_INFO (Ask for More Info). — Booking: BOOK_NOW (Book Now), BOOK_TRAVEL (Book Travel), REQUEST_TIME (Request Time), MAKE_AN_APPOINTMENT (Make an Appointment), BOOK_A_CONSULTATION (Book a Consultation), GET_SHOWTIMES (Get Showtimes), BUY_TICKETS (Buy Tickets). — App: INSTALL_APP (Install App), INSTALL_MOBILE_APP (Install Mobile App), USE_APP (Use App), USE_MOBILE_APP (Use Mobile App), DOWNLOAD (Download), PLAY_GAME (Play Game), OPEN_INSTANT_APP (Open Instant App), UPDATE_APP (Update App). — Lead Gen: APPLY_NOW (Apply Now), INQUIRE_NOW (Inquire Now), GET_OFFER (Get Offer), GET_DIRECTIONS (Get Directions). — Engagement: SUBSCRIBE (Subscribe), FOLLOW_PAGE (Follow Page), EVENT_RSVP (RSVP), DONATE (Donate), DONATE_NOW (Donate Now), RAISE_MONEY (Raise Money), REFER_FRIENDS (Refer Friends). — Media: WATCH_VIDEO (Watch Video), WATCH_MORE (Watch More), LISTEN_NOW (Listen Now), LISTEN_MUSIC (Listen Music), WATCH_LIVE_VIDEO (Watch Live Video).`, + }, + { + name: 'cards', + type: 'array', + required: false, + description: `Cards for a STATIC carousel ad (2–10). Distinct from Advantage+ catalog carousel: do NOT combine cards with product_set_id, image_hash, image_url, or video_id. Each card needs exactly one image (image_hash or image_url); a video card sets video_id and may omit the image, in which case the video's default thumbnail is used. Top-level link_url and call_to_action_type act as per-card fallbacks.`, + }, + { + name: 'description', + type: 'string', + required: false, + description: `Short description text. Maps to link_data.description for image ads, video_data.link_description for video ads, or template_data.description for carousel ads.`, + }, + { + name: 'headline', + type: 'string', + required: false, + description: `Short headline shown under the image (link_data.name), video (video_data.title), or carousel cards (template_data.name).`, + }, + { + name: 'image_hash', + type: 'string', + required: false, + description: `Hash of a pre-uploaded image asset. For image ads: the ad image (required — exactly one of image_hash or image_url). For video ads: the video thumbnail / cover image shown before the video plays (required — exactly one of image_hash or image_url). Not used for catalog carousel ads.`, + }, + { + name: 'image_url', + type: 'string', + required: false, + description: `URL of an image. For image ads: the ad image (required — exactly one of image_hash or image_url). For video ads: the video thumbnail / cover image shown before the video plays (required — exactly one of image_hash or image_url). Not used for catalog carousel ads.`, + }, + { + name: 'instagram_user_id', + type: 'string', + required: false, + description: `Instagram User (IG Business Account) ID for IG placement delivery. Omit and the creative will not deliver on Instagram surfaces.`, + }, + { + name: 'link_url', + type: 'string', + required: false, + description: `Destination URL the creative clicks to (e.g. "https://example.com/landing"). Required for image and catalog carousel ads; optional for video ads. Always include https:// scheme; if omitted the tool prepends it automatically. For catalog carousel ads, this is the default landing URL; product-specific deep links from the catalog may override it at delivery time.`, + }, + { + name: 'message', + type: 'string', + required: false, + description: `Body text shown above the image, video, or carousel. For carousel ads, may contain template strings like {{product.name}} filled from the catalog at delivery time.`, + }, + { + name: 'name', + type: 'string', + required: false, + description: `Name of this ad creative as seen in the ad account's library. Strongly recommended.`, + }, + { + name: 'product_set_id', + type: 'string', + required: false, + description: `ID of a product set from the advertiser's catalog. Required for Advantage+ catalog carousel ads. When provided, creates a carousel creative that dynamically populates card content from the catalog at delivery time. Do not provide image_hash, image_url, or video_id when using product_set_id.`, + }, + { + name: 'self_ai_disclosure', + type: 'string', + required: false, + description: `AI-generated content disclosure for the creative. Set to "OPT_IN" to declare that this creative contains media created or edited with a third-party generative AI tool; set to "OPT_OUT" to declare it does not. Omit if unknown. Only these two exact UPPER_CASE values are accepted. When you opt in, Meta may display an "AI info" label on the ad; whether it appears depends on the regions the ad is delivered to and their AI-transparency requirements.`, + }, + { + name: 'video_id', + type: 'string', + required: false, + description: `ID of a pre-uploaded video on the ad account. Required for video ads. Not used for catalog carousel ads.`, + }, + ], + }, + { + name: 'facebookadsmcp_ads_create_custom_audience', + description: `Creates a new custom audience under the specified ad account. Supports five audience subtypes: CUSTOM (customer list / DFCA), WEBSITE (WCA), ENGAGEMENT (ECA), MOBILE_APP (MACA), and LOOKALIKE (LAL). + + ## When to use: + - Call this tool when the user wants to create a new custom audience. + - For CUSTOM: user says "create customer list", "audience for a csv file", "create DFCA". + - For WEBSITE: user says "create website audience", "retarget website visitors", "create WCA", "pixel audience". + - For ENGAGEMENT: user says "create Instagram audience", "retarget Instagram engagers", "create ECA", "people who engaged with my IG profile", "Page followers audience", "people who liked my Page", "Page engagers", "shop visitors", "people who viewed products in my shop", "shopping audience", "people who viewed my Marketplace listings", "Marketplace catalogue viewers", "on-Facebook listings audience", "lead form audience", "people who opened my lead form", "people who submitted my lead form", "lead generation audience", "Instant Experience audience", "people who opened my Instant Experience", "Canvas audience", "people who clicked links in my Instant Experience". + - For MOBILE_APP: user says "create mobile app audience", "retarget app users", "create MACA", "people who opened my app", "most active app users", "top app spenders", "users by purchase amount". + - For LOOKALIKE: user says "create lookalike audience", "create LAL", "people similar to my customers", "expand my audience", "lookalike of ", "find similar users", "1% lookalike", "broad lookalike". + + ## When NOT to use: + - Do NOT use for uploading user data to CUSTOM audiences — use ads_update_custom_audience_users after creating the audience. + - Do NOT use for modifying existing audiences — use ads_update_entity instead. + + ## CRITICAL: + ### Subtype-specific requirements: + + #### CUSTOM (DFCA - Data File Custom Audience): + - customer_file_source is REQUIRED. + - The audience is created empty. You must use ads_update_custom_audience_users to add users after creation. + - ads_update_custom_audience_users accepts PII either raw or pre-hashed — it normalizes and SHA-256 hashes raw values for you before they reach the API. + + #### WEBSITE (WCA - Website Custom Audience): + - rule is REQUIRED. Must contain event_sources with type "pixel" and the pixel ID. + - If the user does not know their pixel ID, use the ads_get_datasets tool to look up pixel IDs for their ad account. + - The audience auto-populates from pixel events — no manual user upload needed. + - Three main targeting flows with REQUIRED template field: + 1. All website visitors: use template "ALL_VISITORS" with filter {"field":"url","operator":"i_contains","value":""} (empty value = match all URLs). + 2. People who visited specific pages: use template "VISITORS_BY_URL" with filter {"field":"url","operator":"i_contains","value":""}. + 3. Visitors by time spent: use template "TOP_TIME_SPENDERS" with aggregation {"type":"time_spent","method":"percentile","operator":"in_range","value":{"from":75,"to":100}}. + - The template field is REQUIRED in each rule. Always include it. + - You can also filter on specific standard/custom events: "Purchase", "AddToCart", "Lead", "ViewContent", "CompleteRegistration", "InitiateCheckout". Use filter {"field":"event","operator":"eq","value":""}. + - When the user wants multiple rules, ASK whether they want "any" (OR) or "all" (AND) logic. The inclusions operator controls this: "or" means match ANY rule, "and" means match ALL rules. Default is "or". + - URL rules can be refined by frequency via aggregation {"type":"count","method":"absolute","operator":">=","value":5}. + - CRITICAL: For "all visitors", use url filter with empty value, NOT event filter with "PageView". + - Example rule (all visitors, 30 days): {"inclusions":{"operator":"or","rules":[{"event_sources":[{"type":"pixel","id":""}],"retention_seconds":2592000,"filter":{"operator":"and","filters":[{"field":"url","operator":"i_contains","value":""}]},"template":"ALL_VISITORS"}]}} + - Example rule (specific pages by URL): {"inclusions":{"operator":"or","rules":[{"event_sources":[{"type":"pixel","id":""}],"retention_seconds":2592000,"filter":{"operator":"and","filters":[{"operator":"or","filters":[{"field":"url","operator":"i_contains","value":"nike"}]},{"field":"url","operator":"i_contains","value":""}]},"template":"VISITORS_BY_URL"}]}} + - Example rule (top 25% time spent): {"inclusions":{"operator":"or","rules":[{"event_sources":[{"type":"pixel","id":""}],"retention_seconds":2592000,"filter":{"operator":"and","filters":[{"field":"url","operator":"i_contains","value":""}]},"template":"TOP_TIME_SPENDERS","aggregation":{"type":"time_spent","method":"percentile","operator":"in_range","value":{"from":75,"to":100}}}]}} + - Example rule (Purchase event): {"inclusions":{"operator":"or","rules":[{"event_sources":[{"type":"pixel","id":""}],"retention_seconds":2592000,"filter":{"operator":"and","filters":[{"field":"event","operator":"eq","value":"Purchase"}]},"template":"VISITORS_BY_URL"}]}} + + #### ENGAGEMENT (ECA - Engagement Custom Audience): + - Currently supports Instagram engagement audiences only. + - rule is REQUIRED. Must contain event_sources with type "ig_business" and the Instagram business account ID. + - The audience auto-populates from Instagram engagement events — no manual user upload needed. + - retention_seconds controls how long users stay in the audience (e.g., 31536000 for 365 days, 15552000 for 180 days). For "started following" event, use retention_seconds=0 (people who unfollow are automatically removed). + - Six Instagram engagement event types: + 1. "ig_business_profile_all" — Anyone who visited the profile OR engaged with any post/ad (likes, comments, saves, carousel swipes, button taps, shares). + 2. "INSTAGRAM_PROFILE_FOLLOW" — People who started following this account. Use retention_seconds=0 so unfollowers are removed. + 3. "ig_business_profile_visit" — People who visited the profile, regardless of action taken. + 4. "ig_business_profile_engaged" — People who engaged with a post or ad (likes, comments, saves, carousel swipes, button taps, shares). + 5. "ig_business_profile_user_messaged" — People who sent a message to this account. + 6. "ig_business_profile_ad_saved" — People who saved a post or ad from this account. + - When the user wants multiple rules, ASK whether they want "any" (OR) or "all" (AND) logic. The inclusions operator controls this. + - Rules can mix different IG accounts and event types in the same audience. + - Example rule (all profile engagers, 365 days): {"inclusions":{"operator":"or","rules":[{"event_sources":[{"type":"ig_business","id":""}],"retention_seconds":31536000,"filter":{"operator":"and","filters":[{"field":"event","operator":"eq","value":"ig_business_profile_all"}]}}]}} + - Example rule (followers only): {"inclusions":{"operator":"or","rules":[{"event_sources":[{"type":"ig_business","id":""}],"retention_seconds":0,"filter":{"operator":"and","filters":[{"field":"event","operator":"eq","value":"INSTAGRAM_PROFILE_FOLLOW"}]}}]}} + - Example rule (profile visitors, 180 days): {"inclusions":{"operator":"or","rules":[{"event_sources":[{"type":"ig_business","id":""}],"retention_seconds":15552000,"filter":{"operator":"and","filters":[{"field":"event","operator":"eq","value":"ig_business_profile_visit"}]}}]}} + - Example rule (mixed — engaged OR saved across accounts): {"inclusions":{"operator":"or","rules":[{"event_sources":[{"type":"ig_business","id":""}],"retention_seconds":31536000,"filter":{"operator":"and","filters":[{"field":"event","operator":"eq","value":"ig_business_profile_engaged"}]}},{"event_sources":[{"type":"ig_business","id":""}],"retention_seconds":31536000,"filter":{"operator":"and","filters":[{"field":"event","operator":"eq","value":"ig_business_profile_ad_saved"}]}}]}} + + ##### Facebook Page Engagement (event_sources type: "page"): + - Requires the Facebook Page ID. If the user does not know their Page ID, use the ads_get_ad_account_pages or ads_get_pages_for_business tool to look up Page IDs. + - The audience auto-populates from Page engagement events — no manual user upload needed. + - retention_seconds controls how long users stay in the audience. For "page_liked" (current followers), use retention_seconds=0 (people who unlike/unfollow are automatically removed). + - Seven event types: + 1. "page_liked" — People who currently like or follow your Page. Use retention_seconds=0 so unfollowers are removed. + 2. "page_engaged" — Everyone who engaged with your Page (visited, reacted, shared, commented, clicked links, swiped carousels). + 3. "page_visited" — Anyone who visited your Page, regardless of action taken. + 4. "page_post_interaction" — People who engaged with any post or ad (reactions, shares, comments, link clicks, carousel swipes). + 5. "page_cta_clicked" — People who clicked any call-to-action button on your Page (e.g., "Call", "Message"). + 6. "page_messaged" — People who sent a message to your Page. + 7. "page_post_saved" — People who saved a post from your Page. + - Example rule (current followers): {"inclusions":{"operator":"or","rules":[{"event_sources":[{"type":"page","id":""}],"retention_seconds":0,"filter":{"operator":"and","filters":[{"field":"event","operator":"eq","value":"page_liked"}]}}]}} + - Example rule (all Page engagers, 365 days): {"inclusions":{"operator":"or","rules":[{"event_sources":[{"type":"page","id":""}],"retention_seconds":31536000,"filter":{"operator":"and","filters":[{"field":"event","operator":"eq","value":"page_engaged"}]}}]}} + - Example rule (messaged your Page, 180 days): {"inclusions":{"operator":"or","rules":[{"event_sources":[{"type":"page","id":""}],"retention_seconds":15552000,"filter":{"operator":"and","filters":[{"field":"event","operator":"eq","value":"page_messaged"}]}}]}} + + ##### Shopping Engagement (event_sources type: "shopping_page" or "shopping_ig"): + - For Facebook Shop engagement, use event_sources type "shopping_page" with the Facebook Page ID. If the user does not know their Page ID, use the ads_get_ad_account_pages or ads_get_pages_for_business tool to look up Page IDs. + - For Instagram Shop engagement, use event_sources type "shopping_ig" with the Instagram account ID. + - The audience auto-populates from shopping events — no manual user upload needed. + - Eight event types: + 1. "VIEW_CONTENT" — People who viewed a product detail page in your shop on Facebook or Instagram. + 2. "PDP_CLICK_TO_OFFSITE" — People who viewed a product detail page and then navigated to your website. + 3. "ADD_TO_WISHLIST" — People who saved a product from your shop. + 4. "SHOPS_PAGE_VIEW" — People who viewed your shop on Facebook or Instagram. + 5. "SHOPS_COLLECTION_VIEW" — People who viewed a collection in your shop. + 6. "ADD_TO_CART" — People who added a product to their basket in your shop or through ads with checkout enabled. + 7. "InitiateCheckout" — People who initiated checkout in your shop or through ads with checkout enabled. + 8. "PURCHASE" — People who purchased a product from your shop or through ads with checkout enabled. + - Example rule (viewed products, 180 days, Facebook Shop): {"inclusions":{"operator":"or","rules":[{"event_sources":[{"type":"shopping_page","id":""}],"retention_seconds":15552000,"filter":{"operator":"and","filters":[{"field":"event","operator":"eq","value":"VIEW_CONTENT"}]}}]}} + - Example rule (added to cart, Instagram Shop): {"inclusions":{"operator":"or","rules":[{"event_sources":[{"type":"shopping_ig","id":""}],"retention_seconds":15552000,"filter":{"operator":"and","filters":[{"field":"event","operator":"eq","value":"ADD_TO_CART"}]}}]}} + - Example rule (purchased any products): {"inclusions":{"operator":"or","rules":[{"event_sources":[{"type":"shopping_page","id":""}],"retention_seconds":15552000,"filter":{"operator":"and","filters":[{"field":"event","operator":"eq","value":"PURCHASE"}]}}]}} + + ##### On-Facebook Listings Engagement (event_sources type: "marketplace_listings"): + - For people who engaged with products in your Facebook Marketplace catalogue (organic + sponsored traffic). + - Requires the owning Facebook Page ID — the \`id\` in event_sources is the Page ID, not a separate catalogue ID. If the user does not know their Page ID, use the ads_get_ad_account_pages or ads_get_pages_for_business tool to look up Page IDs. + - The audience auto-populates from Marketplace listing engagement events — no manual user upload needed. + - Supported event types: + 1. "ViewContent" — People who viewed a product detail page through your Marketplace catalogue (organic or sponsored). Do not invent other event names for this source — only use values the user explicitly provides or that are listed here. + - Example rule (viewed Marketplace products, 180 days): {"inclusions":{"operator":"or","rules":[{"event_sources":[{"type":"marketplace_listings","id":""}],"retention_seconds":15552000,"filter":{"operator":"and","filters":[{"field":"event","operator":"eq","value":"ViewContent"}]}}]}} + + ##### Lead Form Engagement (event_sources type: "lead" and/or "ig_lead_generation"): + - For people who interacted with a Facebook and/or Instagram lead generation form. + - Requires the lead form ID (as \`id\`) and the owning Facebook Page ID (as \`owner_id\`). If the user does not know the Page ID that owns the form, use the ads_get_ad_account_pages or ads_get_pages_for_business tool to look up Page IDs. + - Two event_sources types — pair both for the same form to capture engagement on both surfaces: + - "lead" — Facebook-side engagement with the form. + - "ig_lead_generation" — Instagram-side engagement with the form. + - Default behavior: when the user wants to target a form's audience, include BOTH types for the same form_id+owner_id (Meta auto-renders lead forms on both surfaces). Only restrict to one surface if the user explicitly asks. + - The audience auto-populates from lead-form engagement events — no manual user upload needed. + - Multiple lead forms can be combined in a single rule by adding more event_sources entries (one per type × form pair). Mix forms across different owning Pages by varying \`owner_id\` between entries. + - Three event types (only use these; do not invent others): + 1. "lead_generation_opened" — Anyone who opened the lead form. + 2. "lead_generation_dropoff" — People who opened the form but did NOT submit. + 3. "lead_generation_submitted" — People who opened and submitted the form. + - Example rule (anyone who opened the form, FB + IG, 90 days): {"inclusions":{"operator":"or","rules":[{"event_sources":[{"type":"lead","id":"","owner_id":""},{"type":"ig_lead_generation","id":"","owner_id":""}],"retention_seconds":7776000,"filter":{"operator":"and","filters":[{"field":"event","operator":"eq","value":"lead_generation_opened"}]}}]}} + - Example rule (opened but did not submit — drop-off retargeting): {"inclusions":{"operator":"or","rules":[{"event_sources":[{"type":"lead","id":"","owner_id":""},{"type":"ig_lead_generation","id":"","owner_id":""}],"retention_seconds":7776000,"filter":{"operator":"and","filters":[{"field":"event","operator":"eq","value":"lead_generation_dropoff"}]}}]}} + - Example rule (submitted across multiple lead forms, FB + IG): {"inclusions":{"operator":"or","rules":[{"event_sources":[{"type":"lead","id":"","owner_id":""},{"type":"lead","id":"","owner_id":""},{"type":"ig_lead_generation","id":"","owner_id":""},{"type":"ig_lead_generation","id":"","owner_id":""}],"retention_seconds":7776000,"filter":{"operator":"and","filters":[{"field":"event","operator":"eq","value":"lead_generation_submitted"}]}}]}} + + ##### Instant Experience Engagement (event_sources type: "canvas"): + - For people who engaged with an Instant Experience (a.k.a. Canvas) on Facebook or Instagram. + - Requires the Instant Experience ID (as \`id\`, internally called the canvas ID) and the owning Facebook Page ID (as \`owner_id\`). If the user does not know the Page ID that owns the Instant Experience, use the ads_get_ad_account_pages or ads_get_pages_for_business tool to look up Page IDs. + - The audience auto-populates from Instant Experience engagement events — no manual user upload needed. + - Multiple Instant Experiences owned by the same Page can be combined in a single rule by adding more event_sources entries (one per canvas ID, same \`owner_id\`). Mix across Pages by varying \`owner_id\` between entries. + - Two event types (only use these; do not invent others): + 1. "instant_shopping_document_open" — People who opened the Instant Experience. + 2. "instant_shopping_element_click" — People who clicked any link/element inside the Instant Experience. + - Example rule (anyone who opened the Instant Experience, 365 days): {"inclusions":{"operator":"or","rules":[{"event_sources":[{"type":"canvas","id":"","owner_id":""}],"retention_seconds":31536000,"filter":{"operator":"and","filters":[{"field":"event","operator":"eq","value":"instant_shopping_document_open"}]}}]}} + - Example rule (clicked any link in the Instant Experience): {"inclusions":{"operator":"or","rules":[{"event_sources":[{"type":"canvas","id":"","owner_id":""}],"retention_seconds":31536000,"filter":{"operator":"and","filters":[{"field":"event","operator":"eq","value":"instant_shopping_element_click"}]}}]}} + - Example rule (opened across multiple Instant Experiences from the same Page): {"inclusions":{"operator":"or","rules":[{"event_sources":[{"type":"canvas","id":"","owner_id":""},{"type":"canvas","id":"","owner_id":""}],"retention_seconds":31536000,"filter":{"operator":"and","filters":[{"field":"event","operator":"eq","value":"instant_shopping_document_open"}]}}]}} + + #### MOBILE_APP (MACA - Mobile App Custom Audience): + - rule is REQUIRED. Must contain event_sources with type "app" and the App SDK ID (the numeric ID for an app that has integrated the Facebook App Events SDK / FB SDK and is logging app events). It is NOT the App Store / Play Store package name — it is the Meta-issued numeric app ID for the SDK-integrated app. + - If the user does not know their App SDK ID, ask them to provide it; it is the same numeric ID used in their Facebook for Developers app dashboard for the app that integrated the SDK. + - The audience auto-populates from app events logged by that App SDK — no manual user upload needed. + - retention_seconds controls how long users stay in the audience (e.g., 2592000 for 30 days, 15552000 for 180 days; max is 180 days). + - Five main targeting flows: + 1. App launchers / "anyone who opened the app": template "MACA_APP_LAUNCHED_USERS", filter on event "fb_mobile_activate_app". NO aggregation. + 2. Most active users: template "MACA_MOST_ACTIVE_USERS" with event "fb_mobile_activate_app" AND aggregation {"type":"count","method":"percentile","operator":"in_range","value":{"from":,"to":100}} — selects the top N% of app launchers by event count. Only three percentile windows are supported: top 5% (from=95), top 10% (from=90), top 25% (from=75). + 3. Top spenders by purchase amount: template "MACA_TOP_PURCHASE_USERS" with event "fb_mobile_purchase" and aggregation {"type":"sum","field":"_valueToSumInUSD","method":"percentile","operator":"in_range","value":{"from":,"to":100}}. Only three percentile windows are supported: top 5% (from=95), top 10% (from=90), top 25% (from=75). + 4. Mix of inclusion + exclusion: combine an inclusion rule (e.g., app launchers) with an exclusion rule (e.g., top purchasers) to find "engaged users who haven't yet purchased high value". + 5. Custom in-app events: use any app event the advertiser logs via the FB SDK. The event name is supplied by the advertiser — ask the user for the exact event name they log; do NOT guess or fabricate event names. filter {"field":"event","operator":"eq","value":""}. The template field is OPTIONAL for custom advertiser-defined events. + - The template field is REQUIRED for the MACA_APP_LAUNCHED_USERS, MACA_MOST_ACTIVE_USERS, and MACA_TOP_PURCHASE_USERS flows. It can be omitted for custom advertiser events. + - When the user wants multiple rules, ASK whether they want "any" (OR) or "all" (AND) logic. The inclusions operator controls this. + - Different App SDK IDs can be combined in the same audience (multiple event_sources entries or multiple rules). + - Example rule (anyone who opened the app, 30 days): {"inclusions":{"operator":"or","rules":[{"event_sources":[{"type":"app","id":""}],"retention_seconds":2592000,"filter":{"operator":"and","filters":[{"field":"event","operator":"eq","value":"fb_mobile_activate_app"}]},"template":"MACA_APP_LAUNCHED_USERS"}]}} + - Example rule (most active app users — top 25% by event count, 30 days): {"inclusions":{"operator":"or","rules":[{"event_sources":[{"type":"app","id":""}],"retention_seconds":2592000,"filter":{"operator":"and","filters":[{"field":"event","operator":"eq","value":"fb_mobile_activate_app"}]},"template":"MACA_MOST_ACTIVE_USERS","aggregation":{"type":"count","method":"percentile","operator":"in_range","value":{"from":75,"to":100}}}]}} + - Example rule (top 25% purchasers): {"inclusions":{"operator":"or","rules":[{"event_sources":[{"type":"app","id":""}],"retention_seconds":2592000,"filter":{"operator":"and","filters":[{"field":"event","operator":"eq","value":"fb_mobile_purchase"}]},"template":"MACA_TOP_PURCHASE_USERS","aggregation":{"type":"sum","field":"_valueToSumInUSD","method":"percentile","operator":"in_range","value":{"from":75,"to":100}}}]}} + - Example rule (app launchers minus top 25% spenders from another app): {"inclusions":{"operator":"or","rules":[{"event_sources":[{"type":"app","id":""}],"retention_seconds":2592000,"filter":{"operator":"and","filters":[{"field":"event","operator":"eq","value":"fb_mobile_activate_app"}]},"template":"MACA_APP_LAUNCHED_USERS"}]},"exclusions":{"operator":"or","rules":[{"event_sources":[{"type":"app","id":""}],"retention_seconds":2592000,"filter":{"operator":"and","filters":[{"field":"event","operator":"eq","value":"fb_mobile_purchase"}]},"template":"MACA_TOP_PURCHASE_USERS","aggregation":{"type":"sum","field":"_valueToSumInUSD","method":"percentile","operator":"in_range","value":{"from":75,"to":100}}}]}} + - Example rule (custom advertiser-defined event — ask user for the event name they log): {"inclusions":{"operator":"or","rules":[{"event_sources":[{"type":"app","id":""}],"retention_seconds":2592000,"filter":{"operator":"and","filters":[{"field":"event","operator":"eq","value":""}]}}]}} + + #### LOOKALIKE (LAL - Lookalike Audience): + - A lookalike finds NEW people who are most similar to an existing source audience (the "origin"). Use it to expand reach beyond a known set of users. + - origin_audience_id is REQUIRED — the ID of an existing custom audience that this lookalike will be modeled on. The origin can be CUSTOM (DFCA), WEBSITE (WCA), ENGAGEMENT (ECA), or MOBILE_APP (MACA) — but NOT another LOOKALIKE. A lookalike of a lookalike is not allowed. + - If the user does not have an existing source audience, prompt them to first create one (via this same tool with subtype=CUSTOM / WEBSITE / ENGAGEMENT / MOBILE_APP). + - lookalike_ratio is REQUIRED. Range: 0.01 to 0.20 (i.e. 1% to 20%). Default: 0.01 (1%) — recommend 1% as the most similar/smallest audience; users can broaden by raising the value up to 20%. 1% = closest match; 20% = broadest, largest audience but less similar. + - DO NOT ask the user for a country, region, or geographic location. The lookalike is always created with allow_international_seeds=true and is_parent_lal=true — Meta handles the geography automatically. Country is NOT a parameter of this tool. + - rule, customer_file_source, is_value_based, retention_days, prefill, audience_labels are NOT used for LOOKALIKE — do not pass them. + - The audience auto-builds from Meta's modeling — no manual user upload needed. Building takes time after creation; the audience is unavailable for ad delivery until ready. + + ## Response Guidelines: + 1. Confirm the audience was created and provide the audience_id. + 2. For CUSTOM: Remind the user to upload user data via ads_update_custom_audience_users. + 3. For WEBSITE: Inform the user that the audience will auto-populate from pixel events (no manual upload needed). + 4. For ENGAGEMENT: Inform the user that the audience will auto-populate from engagement events (no manual upload needed). + 5. For MOBILE_APP: Inform the user that the audience will auto-populate from app events (no manual upload needed). + 6. For LOOKALIKE: Inform the user that Meta will build the audience by modeling from the origin; it is unavailable for delivery until ready (typically minutes to hours). Mention the chosen ratio (e.g. "1%" for ratio=0.01) so they know how broad it is. + 7. If is_value_based is true (CUSTOM only), inform the user they can include LOOKALIKE_VALUE in the schema when uploading users.`, + params: [ + { + name: 'ad_account_id', + type: 'string', + required: true, + description: `The ad account ID. Format: numeric ID without "act_" prefix.`, + }, + { + name: 'name', + type: 'string', + required: true, + description: `Name for the custom audience.`, + }, + { + name: 'subtype', + type: 'string', + required: true, + description: `The type of custom audience to create. Values: CUSTOM (customer list / DFCA — requires customer_file_source), WEBSITE (WCA — requires rule with pixel event_sources), ENGAGEMENT (ECA — requires rule with ig_business/page/shopping_page/shopping_ig/marketplace_listings/lead/ig_lead_generation/canvas event_sources), MOBILE_APP (MACA — requires rule with app event_sources), LOOKALIKE (LAL — requires origin_audience_id and lookalike_ratio; origin must NOT itself be a LOOKALIKE).`, + }, + { + name: 'advertiser_request', + type: 'string', + required: false, + description: `Capture what the advertiser is actually asking for, in their exact words, quoted from their own messages word for word wherever you can. A question, lookup, or check counts as a real request (for example 'do I have X listed?', 'show me my Y', 'how many Z are left?'), so capture it. Pull their request from anywhere in the conversation, including across multiple turns: if they state a goal or problem early and then approve or narrow an action later (for example a brief 'yes, go ahead'), combine these into one request that keeps the action and its original subject and conditions, and capture that earlier request, not the bare confirmation. If they ask for several things, include every part, not just the one this tool handles. Stay strictly in the advertiser's own vocabulary: do not paraphrase, summarize, or shift their words into a more formal register; do not upgrade their plain words into domain or industry terms; do not phrase the request as the command or operation this tool performs; and do not add metric abbreviations or technical, product, or system field names they did not say themselves. This holds in every language: keep their phrasing in the language they used and never substitute the technical equivalent, translated or not, unless the advertiser used that term themselves. Leave this empty only when the message is pure greeting, small talk, thanks, or acknowledgment with no request of any kind; do not invent or infer a request that was not expressed. Do not include names, contact details, or other personal information.`, + }, + { + name: 'audience_labels', + type: 'string', + required: false, + description: `Optional. For WEBSITE, ENGAGEMENT, and MOBILE_APP subtypes. A single label describing this audience. Labels help find audiences for ads more effectively. Engaged audiences: "qualified_leads", "disqualified_leads", "app_installers", "trial_users", "engaged_users". Customers: "high_value_customers", "low_value_customers", "at_risk", "disengaged", "customer_leads". Ignored for CUSTOM subtype.`, + }, + { + name: 'customer_file_source', + type: 'string', + required: false, + description: `Required for CUSTOM subtype only. How the customer data was sourced. Values: USER_PROVIDED_ONLY (advertiser collected directly), PARTNER_PROVIDED_ONLY (from a partner), BOTH_USER_AND_PARTNER_PROVIDED (mixed sources). Ignored for WEBSITE subtype.`, + }, + { + name: 'description', + type: 'string', + required: false, + description: `Optional description for the audience.`, + }, + { + name: 'is_value_based', + type: 'boolean', + required: false, + description: `Optional. For CUSTOM subtype only. Set to true to create a value-based audience for use with value optimization. Default: false. Ignored for WEBSITE subtype.`, + }, + { + name: 'lookalike_ratio', + type: 'number', + required: false, + description: `Required for LOOKALIKE subtype only. The share of the population to match. Range: 0.01 (1%, closest match, smallest audience) to 0.20 (20%, broadest, largest audience). Default: 0.01 (1%). Ignored for non-LOOKALIKE subtypes. DO NOT ask the user for a country — geography is handled automatically (allow_international_seeds=true).`, + }, + { + name: 'origin_audience_id', + type: 'string', + required: false, + description: `Required for LOOKALIKE subtype only. The numeric ID of an existing custom audience to model this lookalike on, passed as a string. The origin can be CUSTOM (DFCA), WEBSITE (WCA), ENGAGEMENT (ECA), or MOBILE_APP (MACA), but NOT another LOOKALIKE. Ignored for non-LOOKALIKE subtypes.`, + }, + { + name: 'prefill', + type: 'boolean', + required: false, + description: `Optional. For WEBSITE, ENGAGEMENT, and MOBILE_APP subtypes. Whether to backfill the audience with historical data. Default: true. Ignored for CUSTOM subtype.`, + }, + { + name: 'retention_days', + type: 'integer', + required: false, + description: `Optional. For CUSTOM subtype only. Number of days to retain audience members. Range: 1-180. Default: 180. For WEBSITE, retention is set via retention_seconds in the rule JSON.`, + }, + { + name: 'rule', + type: 'string', + required: false, + description: `Required for WEBSITE, ENGAGEMENT, and MOBILE_APP subtypes. MUST be a JSON-encoded string, NOT a raw JSON object. Pass the rule as a single string value like "{\\"inclusions\\":{...}}", not as a nested object. Top-level structure: {"inclusions":{...}, "exclusions":{...}}. "exclusions" is optional. Each block has: {"operator":"or","rules":[, , ...]}. Each rule has: {"event_sources":[{"type":"","id":""}], "retention_seconds":, "filter":{"operator":"and","filters":[...]}, "template":"