Skip to main content
Glama

Metadata MCP Connector

Create Campaign — Channel First / Native Structure (N×N×N)

create_native_structure_campaign

Create a multi-channel campaign using the platform's "Channel First Structure" (N×N×N) — the UI also labels it "Native Structure" / "Platform-First Structure". Your audiences, ads, and offers are organized into the ad platform's NATIVE structure (the WizNativeAdContainer entity) and optimized TOGETHER by the platform's own algorithm — ~1 experiment per channel (LinkedIn is the only exception: one experiment per container), with unified reporting and pacing. You specify the structure as explicit per-container groupings (each container is one audience + its ad_offers rows); the "N×N×N" label refers to the native structure holding your full set of audiences, ads, and offers together (it is NOT one isolated experiment per combination — that's Precision Optimization, create_campaign). Platform product copy: "Traditional hierarchical setup that leverages native platform algorithms while maintaining Metadata's pacing control, unified user experience and reporting." Sets channel.structureType=NATIVE.

                KEYWORDS (for tool discovery — many users ask by the math, not the product name): NxNxN, N×N×N, "NxNxN campaign", Channel First Structure, Native Structure, Platform-First Structure, channel-first, platform-first, native platform structure, WizNativeAdContainer, container-based campaign, traditional / hierarchical structure, unified reporting.

                NAMING CONVENTION (from the platform UI's "Campaign Structure" selector — match these exact labels):
                  • "Channel First Structure (NxNxN)" = THIS tool (`create_native_structure_campaign`); the UI also shows "Native Structure" / "Platform-First Structure". Audiences/ads/offers grouped in the platform's native structure and optimized together (~1 experiment per channel). "native" / "platform" here = the native ad platform (LinkedIn / Facebook / Google) whose algorithms this leans on — and structureType=NATIVE matches.
                  • "Precision Optimization (1x1x1)" = the OTHER tool, `create_campaign` — each audience×ad×offer combination tested as its own isolated experiment. Metadata's signature approach; the DEFAULT.
                Routing anchor: "native" / "channel first" / "platform first" / "NxNxN" → this tool; "precision" / "1x1x1" → create_campaign.

                ════════════════════════════════════════════════════════════════════════════════
                WARNING: TWO CAMPAIGN-CREATION TOOLS EXIST — READ BEFORE PICKING ONE
                ════════════════════════════════════════════════════════════════════════════════
                CRITICAL: REMEMBER — HOW TO CHOOSE THE STRUCTURE (the user's decision always wins):
                  1. If the user explicitly names one, RESPECT IT — "native" / "Native Structure" / "Channel First" / "Platform-First" / "NxNxN" → create_native_structure_campaign (this tool); "precision" / "Precision Optimization" / "1x1x1" → create_campaign.
                  2. ELSE, if the request involves MANY ads AND audiences (Precision Optimization makes one experiment per combination — a large experiment count), ASK the user which structure they want before creating — do not guess.
                  3. OTHERWISE, DEFAULT to Precision Optimization (1×1×1) → create_campaign (NOT this tool).
                The two build structurally different, non-swappable campaigns — so once the user decides, respect that decision for the rest of the campaign.

                USE THIS TOOL (`create_native_structure_campaign`) — "Channel First" / "Native Structure" (N×N×N) — WHEN:
                  • The user says "native", "Native Structure", "Channel First Structure", "channel-first", "Platform-First Structure", "platform-first", "NxNxN", "N×N×N", "WizNativeAdContainer", or asks for the traditional / hierarchical / native-platform structure.
                  • The user wants the ad platform's native structure to group and optimize audiences/ads/offers together (~1 experiment per channel, unified reporting) rather than an isolated experiment per combination.
                  • The user provides explicit per-container groupings of one audience + its (ad, offer) pairs.

                USE `create_campaign` ("Precision Optimization", 1×1×1) INSTEAD — DO NOT USE THIS TOOL — WHEN:
                  • The user says "Precision Optimization", "precision", "1x1x1", "1×1×1", or wants every audience×ad×offer combination tested separately in its own experiment.
                  • The user wants maximum budget control / fastest per-combination learning.
                  • No structure was specified — Precision Optimization (`create_campaign`) is the DEFAULT.

                If you're unsure whether the user wants isolated per-combination experiments (→ `create_campaign`, Precision Optimization) or the native-platform grouped structure (→ this tool, Channel First), ASK which structure they want — switching modes after creation requires rebuilding the campaign from scratch.

                The two tools produce structurally different campaigns on the backend (channel.structureType = METADATA for create_campaign, = NATIVE for this tool) and are NOT swappable mid-campaign. Pick correctly the first time.
                ════════════════════════════════════════════════════════════════════════════════

                WHEN TO USE THIS TOOL:
                - The user explicitly asks for "Channel First Structure" / "Native Structure" / "Platform-First Structure" / N×N×N / WizNativeAdContainer / the native-platform structure.
                - You want the ad platform's algorithm to optimize audiences/ads/offers together under a unified structure (~1 experiment per channel) instead of isolating every combination.
                - You want a compact, platform-managed experiment footprint with unified reporting.

                WHEN NOT TO USE THIS TOOL:
                - The user wants "Precision Optimization" (1×1×1) — every combination tested as its own isolated experiment — use `create_campaign`.
                - No structure was specified (default is Precision Optimization) — use `create_campaign`.

                CAMPAIGN TYPES (same as create_campaign):
                1. "Lead Gen" — only Lead Gen (LG) offers allowed in any container.
                2. "Brand Awareness" — both LG and LP offers allowed.

                WARNING: OFFER-CHANNEL CHANNEL-LOCK STILL APPLIES
                Lead Gen offers are channel-locked at the offer's creation time. A container under `linkedin.containers` MUST reference an offer whose data.channel='LINKEDIN' (or any Landing Page offer). Cross-channel reuse silently produces a non-serving campaign. The server does NOT validate the pairing. Same rules as create_campaign:
                - linkedin.containers[i].ad_offers[].offer   → LINKEDIN-locked LG OR any LP
                - facebook.containers[i].ad_offers[].offer   → FACEBOOK-locked LG OR any LP
                - instagram.containers[i].ad_offers[].offer  → FACEBOOK/INSTAGRAM-locked LG OR any LP
                - google.containers[i].ad_offers[].offer     → Landing Page offers ONLY (Google Ads has no native LG form support)
                - reddit.containers[i].ad_offers[].offer     → Landing Page offers ONLY (Reddit has no native LG form support)
                - microsoft.containers[i].ad_offers[].offer  → MICROSOFT_ADS-locked LG OR any LP

                Each container is one audience × a LIST of (ad, offer) pairs (`ad_offers`, 1+). Put several pairs in one container to group multiple ads/offers under the same audience — the platform's native ad-set with multiple Ad/Offer rows. "1 audience → 3 ads → 1 offer" is ONE container whose `ad_offers` holds 3 pairs that repeat the same `offer` id. (Each pair is exactly one ad with one offer; to run the same audience as two separate ad-sets, send two containers instead.)

                WARNING: SEARCH CHANNELS ARE KEYWORD-FIRST — `audience` IS OPTIONAL FOR GOOGLE ADS & MICROSOFT ADS
                Google/Microsoft search ad groups are keyword-targeted, so a search container may OMIT `audience` and run keyword-only (just keywords + ad_offers). The platform allows audience-less native search containers, so this tool no longer forces an audience there. `audience` STAYS REQUIRED for the audience-targeted social channels (LinkedIn / Facebook / Instagram / Reddit). A search container with neither an audience nor keywords has nothing to target and is rejected.

                REQUIRED FIELDS:
                - campaignType: "Lead Gen" | "Brand Awareness"
                - name: unique campaign name
                - startDate / endDate: YYYY-MM-DD
                - budgetGroup: budget group name
                - At least one channel block with a non-empty containers list

                PER-CHANNEL SHAPE:
                Every channel block (linkedin / facebook / instagram / google / reddit / microsoft) takes:
                  containers: [
                    {
                      name: "string (<=255 chars) — visible in the platform UI",
                      audience: "audience name (resolved server-side via the same matched-poll create_campaign uses). REQUIRED for LinkedIn/Facebook/Instagram/Reddit; OPTIONAL for Google/Microsoft search (omit it for keyword-only ad groups)",
                      ad_offers: [ {ad: <int WizCreative ID>, offer: <int offer ID>}, ... ]   // 1+ Ad/Offer pairs sharing this container's audience; repeat the same offer id to run several ads on it,
                      target_group: "optional Targeting Group name (singular — one per container)",
                      exclude_audience: "optional audience name to use as the exclude group for this container"
                    }
                  ]

                SEARCH-CHANNEL EXTRAS (Google Ads + Microsoft Ads, per container):
                  keywords: ["string", ...]            // free-text phrases — created in the keyword library and grouped into this container's keyword group
                  keyword_ids: [<int>, ...]            // optional; existing keyword-library IDs (from list_keywords) to put in this container's group — unioned with `keywords`
                  match_type: "PHRASE"|"BROAD"|"EXACT" // optional; match type for this container's keyword group (default PHRASE)
                  negative_keyword_lists: ["name", ...] // optional; applied at the channel level
                On a search channel `audience` is OPTIONAL: a keyword-only container (keywords + ad_offers, no audience) is valid and is how non-brand / pure-keyword search is normally structured. You can still pass an audience to layer one on. The keyword group is created and linked to the container automatically. A search container with neither an audience nor keywords (`keywords`/`keyword_ids`) is rejected (nothing to target).

                AUDIENCE NAME RESOLUTION:
                - `audience` and `exclude_audience` are NAMES, not IDs. Same lookup path as create_campaign — the tool waits up to ~20s per channel for a freshly-created audience to become attachable (firmographics + FB/IG can take 8-15s).
                - Audience names are resolved BEFORE any container elements are created: a container whose `audience` cannot be resolved is dropped up front (no keyword group or other element is left behind for it) and the failure is reported in the response `errors[]`. The error states whether retrying can help ("may still be matching") or not ("not available on this channel") — do NOT blind-retry the latter. The rest of the campaign still gets built.
                - On Google/Microsoft search containers `audience` may be omitted entirely (keyword-only) — only names that are actually provided are resolved, so leaving it out costs no polling and raises no error.

                AD AND OFFER ARE IDS (one `ad_offers` pair = one ad + one offer):
                - `ad` is the ID returned by `create_update_image_ad` or `create_update_video_ad`.
                - `offer` is the ID returned by `create_update_offer`. The tool looks up each unique offer to discover whether it's a Landing Page or Lead Gen Form and routes it into the right `nativeAds[].landingPageId` vs `nativeAds[].leadGenFormId` slot. Every pair becomes one `nativeAds[]` entry under the container.

                BID STRATEGY (automatic):
                Same defaults as create_campaign:
                  - LinkedIn / Facebook / Instagram → AUTO_BID
                  - Google Ads / Microsoft Ads      → TARGET_SPEND (device modifiers reset)
                  - Reddit                          → objective-driven, mirroring the platform UI:
                    Lead Gen forces CLICKS (Traffic) + AUTO_BID; Brand Awareness keeps
                    IMPRESSIONS + MANUAL_BID. Adjust later with update_reddit_channel_settings.

                WHAT THE TOOL DOES FOR YOU (single call, no follow-up needed):
                Creates the campaign shell and its budget group, enables each requested
                channel and sets it to the NATIVE structure, builds the per-container
                keyword group on Google / Microsoft Ads search channels, creates one
                native ad container per grouping with its ad/offer pairs, applies the
                bid strategy, and sets the schedule. The response reflects the final
                campaign state — you do not need to sequence or repeat any of this.

                RESPONSE: Same top-level shape as create_campaign (`success`, `campaign_id`, `campaign_url`, `status`, `optimization_group`, `summary`, `full_response`), including the `refusal="name_taken"` refusal when the account already holds a campaign under the requested name (only the name is the problem: call again with a different name, never with the same one). The per-channel breakdown returns a `containers` array — `[{id, name, experiment_id, audience_group_id, targeting_group_id, group_exclude_id, keyword_group_id, native_ads: [{id, ad_id, landing_page_id, lead_gen_form_id}]}]` — instead of the legacy `audiences`/`ad_groups`/`lead_gen_forms` arrays. On partial failures, returns success=false plus `errors[]` with per-container detail.

                EXAMPLE (Lead Gen, LinkedIn-only, two containers):
                create_native_structure_campaign(campaign_data={
                    "campaignType": "Lead Gen",
                    "name": "Q3_ABM_Native_2026",
                    "startDate": "2026-06-01",
                    "endDate": "2026-08-31",
                    "budgetGroup": "BoFu",
                    "linkedin": {
                        "containers": [
                            {"name": "IT Mgrs > Demo > Form",
                             "audience": "IT Managers - NA",
                             "ad_offers": [{"ad": 156502, "offer": 57538}]},
                            {"name": "CTOs > Demo > Form",
                             "audience": "CTOs - Tech",
                             "ad_offers": [{"ad": 156485, "offer": 57504}]}
                        ]
                    }
                })

                EXAMPLE (one audience → 3 ads on the SAME offer, ONE container — the multi-ad ad-set):
                create_native_structure_campaign(campaign_data={
                    "campaignType": "Brand Awareness",
                    "name": "Q3_3Ads_1Offer_2026",
                    "startDate": "2026-06-01",
                    "endDate": "2026-07-31",
                    "budgetGroup": "Brand",
                    "facebook": {
                        "containers": [
                            {"name": "ICP > 3 creatives > TOF offer",
                             "audience": "26-Q2-ICP-AllPersonas-NA",
                             "ad_offers": [
                                 {"ad": 210801, "offer": 67269},
                                 {"ad": 210802, "offer": 67269},
                                 {"ad": 210803, "offer": 67269}
                             ]}
                        ]
                    }
                })

                EXAMPLE (Brand Awareness, Google + LinkedIn, with keywords):
                create_native_structure_campaign(campaign_data={
                    "campaignType": "Brand Awareness",
                    "name": "Q3_Brand_Native_2026",
                    "startDate": "2026-06-01",
                    "endDate": "2026-07-31",
                    "budgetGroup": "Brand",
                    "linkedin": {
                        "containers": [
                            {"name": "Professionals A",
                             "audience": "Professional Audience",
                             "ad_offers": [{"ad": 173669, "offer": 57538}]}
                        ]
                    },
                    "google": {
                        "containers": [
                            {"name": "CRM Search",
                             "audience": "Tech Decision Makers",
                             "ad_offers": [{"ad": 175260, "offer": 57600}],
                             "keywords": ["crm software", "lead gen tools"],
                             "negative_keyword_lists": ["Competitor Brands"]}
                        ]
                    }
                })

                EXAMPLE (Brand Awareness, Google keyword-only — NO audience, pure non-brand search):
                create_native_structure_campaign(campaign_data={
                    "campaignType": "Brand Awareness",
                    "name": "Q3_NonBrand_Search_2026",
                    "startDate": "2026-06-01",
                    "endDate": "2026-07-31",
                    "budgetGroup": "Search",
                    "google": {
                        "containers": [
                            {"name": "Incident Response",
                             "ad_offers": [{"ad": 210804, "offer": 67269}],
                             "keywords": ["incident response platform", "on-call alerting"],
                             "negative_keyword_lists": ["Competitor Brands"]}
                            # note: no "audience" key — keyword-targeted ad group
                        ]
                    }
                })

                BEST PRACTICES:
                1. Decide each container's audience and its `ad_offers` pairs up front — this tool will not invent pairings for you. Group several ads under one audience by adding pairs to `ad_offers`. On Google/Microsoft search, audience-less (keyword + ad_offers) containers are valid; pure non-brand search normally has no audience. On Reddit, targeting is the `target_group` (create_reddit_target_group) and `audience` is optional — an audience only exists on a channel the account has CONNECTED, so naming one on a disconnected Reddit loses the container.
                2. Use descriptive container names — they show up in the platform UI as the ad-set/ad-group label.
                3. For Google Ads, put related keywords in the same container (each container becomes one keyword group). Don't pile dozens of unrelated keywords into a single container.
                4. Use Brand Awareness for any container that needs Landing Page offers — Lead Gen campaigns reject LP offers.
                5. The campaign URL is returned in the response: https://platform.metadata.io/hub/advertise/campaigns/{campaign_id}

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
campaign_dataYesComplete Channel First / Native Structure (N×N×N) campaign configuration. Each channel takes a `containers` list; each container groups one audience with an `ad_offers` list of {ad, offer} pairs.

Schema Changelog

Changes observed during successful MCP inspections.

  1. First observed

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The annotations only convey readOnlyHint=false and destructiveHint=false (a mutating create), so the description carries the burden — and it over-delivers: discloses the ~20s audience-matching poll, up-front container dropping on unresolvable audiences, partial-failure semantics (success=false + errors[], rest still built), the silent non-serving risk of channel-lock violations, automatic bid strategy per channel, and the single-call no-follow-up behavior. No contradiction with annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Well-structured with visible headers, boxed warnings, routing anchors front-loaded, and examples at the end. However it is verbose and some content is repeated — the keyword/routing lists and the 'WHEN TO USE / WHEN NOT TO USE' sections restate material already covered in 'HOW TO CHOOSE THE STRUCTURE' and 'USE THIS TOOL WHEN'. Earns its place overall, but a tighter edit would remove the duplicates.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a six-channel nested-container tool with no output schema, the description covers everything needed to call it correctly: required fields, per-channel shape, search-channel extras, audience resolution behavior, bid defaults, response shape (including refusal='name_taken' and the containers[] format), channel-lock constraints per channel, and best practices. The response section compensates for the missing output schema.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3; the description adds real semantic value on top: what a 'container' means (one audience + 1+ ad_offers pairs), how to encode multi-ad/multi-offer grouping (repeated offer id), keyword-only search containers with audience omitted, Reddit's target_group vs audience nuance, and four worked examples that clarify the ad_offers repetition pattern. Minor gap: budget/location semantics appear only in the schema, which is acceptable at this coverage, so 4 rather than 5.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The first sentence names a specific verb ('Create') and resource ('multi-channel campaign') with an exact structural flavor ('Channel First Structure' N×N×N') and ties it to a concrete backend artifact (channel.structureType=NATIVE). It fully distinguishes from the sibling create_campaign (Precision Optimization 1×1×1) by contrast, and even maps the vocabulary ('native'/'channel first'/'NxNxN' → this tool) so an agent cannot confuse them.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Exceptionally explicit guidance: a numbered decision procedure (explicit user request → respect it; many ads+audiences → ask; otherwise default to create_campaign), a dedicated 'WHEN TO USE' list, a 'WHEN NOT TO USE' list naming the exact alternative tool, and a warning that the two modes are non-swappable after creation. Nothing is left to inference.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Try in Browser

Glama MCP Gateway

Add one secure layer between your agents and this server.

Resources