MCP App Store
by KeenEthics

Overview

Connect your Opteo account to analyse advertising performance, investigate account issues, review optimisation recommendations, research creatives, and make supported campaign changes. Work with the advertising accounts available to you through Opteo, including Google Ads, Microsoft Advertising, Meta, LinkedIn, and TikTok.

Tools

bulk_categorise

ChatGPT
Categorise a large (50+) list of short strings (search terms, keywords, headlines, etc) against a schema you define, using a cheap dedicated model — so you never have to read the list yourself. Workflow: 1. With run_python, write the rows to a JSONL file in the sandbox (one flat JSON object per line) — e.g. df[["keyword"]].to_json("/mnt/files/terms.jsonl", orient="records", lines=True). 2. Call this tool with that path, your instructions, the outputColumns to fill, and a few examples. Remember that this tool has none of your context, so instructions must be self-contained. 3. You get back outputPath (a JSONL of the original rows merged with the filled columns) plus a summary (row count, per-column value distribution, how many cells came back "UNCATEGORISED", and a small sample). Read or process the result file with run_python. Use allowedValues for any column with a fixed taxonomy — it is validated and keeps labels consistent. Cells the model cannot confidently fill come back as "UNCATEGORISED". From run_python via opteo_tools the call paces itself to the RPC window: rows it could not reach come back untouched in summary.remainingPath, so call again with that path as inputPath until summary.remainingRows is 0. Make it the first thing an exec does, one call per exec.

bulk_tag_creatives

ChatGPT
Tag a batch of ad creatives against a tag schema, using cheap multimodal models — so you never have to open each creative yourself. Platform-agnostic: it reads each row's canonical creative fields (image_urls, video_url, copy), so it works for any source — competitor ads, your own account's creatives, or a JSONL you build yourself. Unlike bulk_categorise (text only), this LOOKS AT the creative, so it can fill visual tags like asset type and creative format. Image creatives are tagged from their images; VIDEO creatives (rows with a video_url) are watched in full (motion and audio) with a cheap Gemini video model. Workflow: 1. Get creatives into a sandbox JSONL where each row carries image_urls, video_url, and/or copy (a platform feeder can emit this directly; otherwise normalise your source into these fields with run_python). 2. Call this tool with that file path and the outputColumns to fill (REQUIRED — the invoking skill supplies the tag set; a call with no columns is rejected). Optionally add instructions + examples. 3. You get back outputPath (a JSONL of the original rows merged with the filled tag columns) plus a summary (totalRows, per-column value distribution for closed-set columns, how many cells came back "UNCATEGORISED", and a small sample). Read or analyse the result with run_python. This tool tags every row it is given. If you want duplicate creatives tagged only once across a larger workload, dedupe before chunking in the orchestrator. Cells the model can't confidently fill come back "UNCATEGORISED". A call's runtime scales with its row count — a video creative takes roughly 5-25 seconds, an image a few, and the tool works through fifty at a time internally. NEVER run this tool in parallel with itself: issue one call, wait for it to return, then issue the next. Running several at once multiplies that internal concurrency into the models' rate limits and is the one thing that will overload it. Each call stops at a wall-clock budget rather than running indefinitely. If it runs out of time it still returns normally, having tagged as many rows as it could: check summary.remainingRows — when it is above 0, summary.remainingPath is a JSONL of the untagged rows, so call this tool again with that path as inputPath (and a fresh outputPath) until nothing remains. That is the intended way to get through a big set; you do not need to pre-chunk to avoid timeouts. Chunking is still worth it for failure isolation and visible progress: SPLIT the JSONL with run_python and work through the chunks SEQUENTIALLY, each with its own outputPath, then concatenate the outputs. Around 150-250 rows per chunk works for both images and videos. Each chunk's output persists independently, so a failure only costs that chunk. From run_python via opteo_tools the same contract holds on a shorter leash: the call paces itself to the RPC window instead of its usual budget, so each call tags a smaller slice and hands the rest back in remainingPath. Make it the first thing an exec does, one call per exec, and loop on remainingPath across execs.

create_or_update_account_group

ChatGPT
Create or update an account group (typically, a client) on the user’s Opteo team. Only provided fields are changed; omitted fields keep their current value. GA4 / Search Console connections cannot be changed via this tool.

create_or_update_saved_prompt

ChatGPT
Create or update saved prompt. Saves a reusable chat prompt to the Opteo prompt library, or edits an existing one — the write counterpart of list_saved_prompts. Team prompts can only be created or edited by team admins; personal prompts only by their owner.

delete_saved_prompts

ChatGPT
Delete saved prompts. Permanently deletes personal or team prompts from the Opteo prompt library, with per-ID results (deleted / failed with the reason). Team prompts can only be deleted by team admins; personal prompts only by their owner.

dismiss_improvements

ChatGPT
Dismiss (defer) one or more active Opteo improvements for the account. This executes the same action as clicking "Dismiss" in the Opteo improvements UI — it removes the improvement from the active queue and defers it for a specified duration. Only dismiss improvements that the user has explicitly asked you to dismiss. Always fetch and present the account's active improvements to the user before dismissing, so they know exactly what will be changed. Do not dismiss improvements speculatively or without clear user intent. Only active improvements can be dismissed. Do not call this tool for improvements with status "completed" or "dismissed" — dismissed improvements are already deferred and cannot be dismissed again. If the user wants to act on a dismissed improvement, tell them to un-dismiss it from the Opteo UI first. Each improvement is identified by its improvementId (a unique integer identifying the improvement). Provide a dismissal duration: "week" defers for 7 days, "month" defers for 30 days, "forever" defers indefinitely. Returns a result per improvement indicating whether it succeeded or failed.

fetch_google_serp

ChatGPT
Search Google and get search engine results page (SERP) data for a query. Use this to get a measure of organic results for a Google Ads search term.

fetch_improvements

ChatGPT
Fetch full details for specific Opteo improvements by ID, including their body and extraDetailsDescription. Use this tool when you need the full content of specific improvements — for example, when you are about to push an improvement and need its extraDetailsDescription to know what to ask the user before proceeding, or when you want to display the full body of a specific improvement. First use list_improvements to discover available improvements and obtain their IDs, then pass those IDs here. Only fetch the improvements you actually need to act on or display in full — do not call this speculatively on large numbers of improvements. Array results are returned as a JSONL dataset, also readable via resources/read.

fetch_issues

ChatGPT
Fetch full details for specific Opteo issues by ID, including their description. Use this tool when you need the full description of specific issues — for example, when you are about to guide the user through resolving an issue and need its description to explain what is wrong and how to fix it. First use list_issues to discover available issues and obtain their IDs, then pass those IDs here. Only fetch the issues you actually need to explain or act on — do not call this speculatively on large numbers of issues. Array results are returned as a JSONL dataset, also readable via resources/read.

fetch_platform_guidance

ChatGPT
Call before first working on a platform in this conversation, unless its guidance is already provided. Returns platform tips matching your available tools. Reuse the guidance across accounts on the same platform.

fetch_url

ChatGPT
Fetch and extract content from an URL.

fetch_widget_output

ChatGPT
Read the result of what the user did in an in-chat widget — the files they uploaded, the assets they picked. Call once the user says they are done (or as soon as you are told the widget has been submitted). If nothing has been submitted yet, ask them to finish, then call again with the same sessionId.

ga4_discover_fields

ChatGPT
Discover GA4 (Google Analytics 4) dimensions and metrics for a property via the live Metadata API — standard fields plus the property’s custom dimensions/metrics. Use this BEFORE ga4_run_query to find valid field API names. Pass a GA4 accountId (ga4_*) from list_accounts as accountId, or an ad-platform accountId to use the GA4 property connected to that account or to its account group. Array results are returned as a JSONL dataset, also readable via resources/read.

ga4_run_query

ChatGPT
Query GA4 (Google Analytics 4) with arbitrary dimensions, metrics, date ranges, and filters via the live Data API. Results are scoped to the linked GA4 data stream, and revenue metrics are reported in the GA4 property currency (see the returned currency field — it can differ from the ad account currency). Invalid field names fail with a Google error message; call ga4_discover_fields first to find valid dimension/metric API names, including custom ones. Pass a GA4 accountId (ga4_*) from list_accounts as accountId, or an ad-platform accountId to query the GA4 property connected to that account or to its account group. Array results are returned as a JSONL dataset, also readable via resources/read.

get_account_context

ChatGPT
Load per-account context (name, platform, currency, account structure, performance targets, performance goals, monthly budget, linked GA4 account, and the index of the account's and its group's context files and signals) for one or more accounts. Call this when starting work on a specific account so you understand its currency, structure, and targets before analysing. Multi-account: pass all accounts you'll be reasoning about in one call.

get_account_group_settings

ChatGPT
Get account group settings. Returns one row per requested account group: name, colour, description (the target-market statement used as chat context), brand terms (comma-separated), industry, competitor URLs, the linked GA4 account ID (read-only — GA4 / Search Console connections are managed in the Opteo UI, not here), and the assigned account IDs (team-wide). Modify settings with create_or_update_account_group. Array results are returned as a JSONL dataset, also readable via resources/read.

get_account_settings

ChatGPT
Get account settings. Returns the full per-account Opteo configuration for the given accounts: budget amount, performance mode (cpa/roas), selected conversion actions, onboarding-wizard visibility, performance goals, description (the target-market statement used as chat context), brand terms, competitor URLs, industry, alert muting (per-user email mutes + team-wide Slack mute), active improvement types, account structure overview, and the assigned user. Description, brand terms, competitors, and industry override the account group's equivalents (get_account_group_settings) when set. The read counterpart of set_account_settings — call it first to see current values and valid identifiers (conversion action IDs, improvement-type titles). For prose account context to reason about performance, prefer get_account_context.

get_user_tool_permissions

ChatGPT
Get the current tool restrictions for a member of your team without changing them. Only team admins can call this operation. Use the member’s Opteo user ID (linkedUsers from list_accounts includes IDs). Returns userId and disallowedTools, a list of canonical permission names.

googleads_apply_changeset

ChatGPT
Apply a Google Ads changeset as ONE atomic request across any mix of entities. Supports independent changes and operations that must be applied together: - Operations must reference each other before they exist (creating a Performance Max campaign with its budget, asset groups, assets and signals; any parent+child created together). - For any non-trivial set of changes, build the changeset programmatically from GAQL results already offloaded in the sandbox. Run googleads_validate_changeset first — it checks the same changeset against Google without writing anything. Explain what this is about to do before calling it. No need to stop for confirmation, just explain. A changeset is stored as JSONL — one operation per line, each {"entity": ..., "operation": "create"|"update"|"remove", "resource": {...}}. Rules: - Changeset order is request order. A temp resource name must be created on an EARLIER line than the line referencing it. - Temp resource names wire operations together: give a created resource a negative id ("resource_name": "customers/1234567890/campaignBudgets/-1") and reference that exact string from later lines ("campaign_budget": "customers/1234567890/campaignBudgets/-1"). Use a distinct negative number per created resource. - `remove` needs only resource.resource_name. - `update` sends an auto-computed field mask: include only the fields you are changing. Set a field to null to CLEAR it; omitting it means "no change". - Fields are snake_case, enums are string names ("PAUSED"), money is micros (150000000 = $150). - Call googleads_discover_resource_fields for an entity before writing lines for it — do not guess field names. Pass exactly ONE accountId. Resource names embed the customer id, so a changeset built for one account is not valid for another. The whole changeset succeeds or fails as one transaction. There is no local cap on operations; Google rejects an over-large request with REQUEST_TOO_LARGE, in which case split the changeset and call again.

googleads_discover_gaql_fields

ChatGPT
Discover Google Ads GAQL fields, segments, metrics, and resources. Use this BEFORE writing a GAQL query to find valid field names and check which fields can be used together.

googleads_discover_resource_fields

ChatGPT
Return the exact fields Google Ads accepts for one resource, as JSON Schema, for use in a Google Ads changeset. Call this before writing changeset operations for an entity you have not used in this conversation. It is derived from the same proto metadata the API validates against, so it is authoritative about field names, nesting, enum values and which fields are immutable — do not guess these. Polymorphic resources (asset, ad, ad_group_ad, campaign, ad_group_criterion, campaign_criterion, …) return only their oneOfGroups index on the first call, because expanding every variant is far larger than you need. Pick the variants you want from that index and call again with variants: ["sitelink_asset"] to get their schema. oneOfGroups keys are field paths: asset_data is a group on the resource itself, ad.ad_data is a group on its nested ad field. Pass bare member names in variants either way. Not every listed member is settable: some are output-only (a location_asset cannot be created by mutation — Google syncs it from a linked Business Profile), some are immutable and so unavailable on update, some are deprecated. Asking for one of those is an error naming the members you CAN set, rather than a schema quietly missing what you asked for. Set at most one member per oneOf group in an actual mutation.

googleads_generate_keyword_historical_metrics

ChatGPT
Get Google Ads historical search volume, competition, and bid-range metrics for known or shortlisted keywords. This is not an idea-generation tool; keyword-idea results already include these metrics. Array results are returned as a JSONL dataset, also readable via resources/read.

googleads_generate_keyword_ideas

ChatGPT
Generate Google Ads keyword ideas from keywords, a page, or a site. Each idea already includes historical search volume, competition, and bid-range metrics; use googleads_generate_keyword_historical_metrics only for a separate known keyword list or historical period. Array results are returned as a JSONL dataset, also readable via resources/read.

googleads_run_gaql_query

ChatGPT
Run a raw Google Ads Query Language (GAQL) query against the account — one query per call. Use when the typed fetch_* tools do not cover the fields/segments/metrics you need. Use googleads_discover_gaql_fields first if uncertain about which fields exist. Add LIMIT for large queries — change_event requires LIMIT <= 10000 and only 30 days lookback. Do not use auction_insights — credentials lack access. To run several queries, make one call per query, or loop inside run_python via opteo_tools. Array results are returned as a JSONL dataset, also readable via resources/read.

googleads_validate_changeset

ChatGPT
Validate a Google Ads changeset without applying it: same parsing and the same request as googleads_apply_changeset, sent with validate_only so Google checks it and writes nothing. Returns an enriched summary of the changeset, to help spot errors before committing to the change. Always run this before googleads_apply_changeset. Parse errors name the offending line number, and Google Ads validation errors come back verbatim, so you can fix the changeset and re-validate as many times as you need at no risk. A changeset is stored as JSONL — one operation per line, each {"entity": ..., "operation": "create"|"update"|"remove", "resource": {...}}. Rules: - Changeset order is request order. A temp resource name must be created on an EARLIER line than the line referencing it. - Temp resource names wire operations together: give a created resource a negative id ("resource_name": "customers/1234567890/campaignBudgets/-1") and reference that exact string from later lines ("campaign_budget": "customers/1234567890/campaignBudgets/-1"). Use a distinct negative number per created resource. - `remove` needs only resource.resource_name. - `update` sends an auto-computed field mask: include only the fields you are changing. Set a field to null to CLEAR it; omitting it means "no change". - Fields are snake_case, enums are string names ("PAUSED"), money is micros (150000000 = $150). - Call googleads_discover_resource_fields for an entity before writing lines for it — do not guess field names. Pass exactly ONE accountId. Resource names embed the customer id, so a changeset built for one account is not valid for another. The whole changeset succeeds or fails as one transaction. There is no local cap on operations; Google rejects an over-large request with REQUEST_TOO_LARGE, in which case split the changeset and call again.

initialise_opteo

ChatGPT
Start here. Returns sandboxId. Required first call before any other Opteo tool — begin a session, bootstrap, set up, initialise (initialize) Opteo. Returns the user's linked ad accounts plus essential guidance for working with their Google Ads, Microsoft Ads, Meta, LinkedIn and TikTok accounts: the sandbox/file model, available skills, and per-platform conventions. Call before listing accounts, fetching campaigns, running GAQL, or any mutation.

linkedin_bulk_mutate

ChatGPT
Apply a file of LinkedIn Ads mutations — Campaign Groups, Campaigns, Conversion Rules, campaign<->conversion associations, Creatives, the image/video assets a Creative can sponsor, and Matched Audiences: company/contact lists (create, add/remove companies or contacts, replace a CSV list), retargeting (incl. website page sets) and predictive audiences, and which audiences a campaign includes or excludes. This is the only LinkedIn mutation tool, including for a single entity on its own: a one-operation file works the same way. Give each operation a negative tempId; any other operation in the same file that needs to reference it (before it is real) writes "optRefId:" followed by that same negative number where a real id would go, e.g. "urn:li:sponsoredCampaignGroup:optRefId:-1". Call linkedin_describe_mutation with an operation's kind (e.g. "campaign.create") for its full field reference. Validates the file itself before applying — the same checks linkedin_validate_bulk_mutate runs — and rejects it, naming every problem, if anything is wrong. Call linkedin_validate_bulk_mutate on the file first anyway: fixing issues there, before the user sees this tool's approval card, is cheaper than fixing them after. Returns one result per operation, in file order, as { tempId, isSuccess, resultData?, error? } — an operation whose dependency failed is never attempted. Absolute path of the JSONL mutation file inside the sandbox (e.g. /mnt/files/launch.jsonl). Write it with run_python or run_bash first. The first line declares the Opteo account id this file is written for: {"account": "li_123"} (the Opteo accountId, in the Account Context section of your instructions). Required. Then one operation per line, each exactly {"kind": ..., "tempId": ..., "payload": {...}}. - kind is <entityType>.<operation>, e.g. "campaign.create". Entities: campaignGroup, campaign, conversion, campaignConversionAssociation, creative, image, video, dmpSegment, dmpSegmentCompanies, dmpSegmentUsers, dmpSegmentListUpload, dmpSegmentEngagementRules, predictiveAudience, campaignAudiences, adPageSet — linkedin_describe_mutation lists every valid kind. Call linkedin_describe_mutation with a kind for its full field reference before writing lines for it. - tempId is a negative integer you choose to name this not-yet-created entity, unique within the file. - payload uses the same field names and shapes linkedin_describe_mutation documents for that kind. A later line references an earlier tempId by writing "optRefId:" followed by that same negative number in place of a real id's digits, e.g. "urn:li:sponsoredCampaignGroup:optRefId:-1". The declared account must be the account this tool is applying to — a mismatch refuses the whole file. This executes directly on MCP — no approval step, always explain what you're about to do first. Array results are returned as a JSONL dataset, also readable via resources/read.

linkedin_describe_entity

ChatGPT
Look up the full field schema for one or more LinkedIn Ads Campaign Management entity types by name.

linkedin_describe_mutation

ChatGPT
Returns the full detail behind one linkedin_bulk_mutate operation kind — field reference, mutation constraints, and any other rules relevant to it. Call before including that kind of operation in a linkedin_bulk_mutate call.

linkedin_describe_report

ChatGPT
Look up the full query-parameter schema (including pivot enum values) and available metric fields for a LinkedIn Ads reporting finder. Call before linkedin_get_metrics.

linkedin_get_entity_by_id

ChatGPT
Fetch a single LinkedIn Ads entity by id.

linkedin_get_metrics

ChatGPT
Get reporting metrics for a linkedin ads account. ━━━ AVAILABLE FINDERS ━━━ analytics Single-pivot analytics finder. Response capped at 15,000 elements. statistics Up-to-3-pivot analytics finder. attributedRevenueMetrics CRM revenue-attribution finder — requires a connected CRM (Dynamics 365, Salesforce, or HubSpot). Only ACCOUNT/CAMPAIGN_GROUP/CAMPAIGN pivots supported; dateRange must be 30-366 days within the last year. ━━━ PROFESSIONAL DEMOGRAPHIC PIVOTS ━━━ Pivoting by MEMBER_COMPANY_SIZE, MEMBER_INDUSTRY, MEMBER_SENIORITY, MEMBER_JOB_TITLE, MEMBER_JOB_FUNCTION, MEMBER_COUNTRY_V2, MEMBER_REGION_V2, MEMBER_COMPANY, MEMBER_COUNTY groups rows by an inferred member attribute ("professional demographics"). LinkedIn deliberately approximates these metrics to protect member privacy — per LinkedIn, results are usually within three units (impressions/clicks/conversions) of the true count, and rows below their reporting minimum are suppressed entirely rather than shown as zero. Because of this, totals summed over these pivots will typically run a little higher than the equivalent number in LinkedIn's Campaign Manager UI, and results can shift with date-range granularity or reporting level (daily rows summed vs. one full-period row, or campaign-level rows added up vs. an account-level row). This is expected LinkedIn behaviour, not a data error in this tool — when reporting numbers broken out by any of these pivots, tell the user the figures are LinkedIn-approximated and may not exactly match the Campaign Manager UI. ━━━ COST PER RESULT ━━━ LinkedIn Campaign Manager's "Results" / "Cost per result" columns are computed client-side — there is no such field in this API. Replicate them by resolving which metric counts as the result for the campaign's objective (table below, keyed by objectiveType), then computing cost ÷ result count — except reach-based results (Brand Awareness), which are cost ÷ (reach ÷ 1,000), the same shape as CPM. BRAND_AWARENESS: Reach → approximateMemberReach — cost per result is per 1,000 reach (CPM-style), not a flat division ENGAGEMENT: Engagement clicks → totalEngagements — already the aggregate of likes/comments/shares/follows/clicks — do not re-sum the individual fields CREATIVE_ENGAGEMENT: Engagement clicks → totalEngagements WEBSITE_VISIT: Landing page clicks → landingPageClicks WEBSITE_TRAFFIC: Landing page clicks → landingPageClicks VIDEO_VIEW: Video views → videoViews LEAD_GENERATION: Leads (Qualified leads if the optimization goal is MAX_QUALIFIED_LEAD) → oneClickLeads (qualifiedLeads for the qualified-lead goal) — the API also returns costPerQualifiedLead pre-computed for the qualified case — use it directly instead of dividing yourself WEBSITE_CONVERSION: Conversions → externalWebsitePostClickConversions JOB_APPLICANT: Landing page clicks → landingPageClicks — NOT jobApplications, despite the objective name — this is LinkedIn's own definition of this objective's key result TALENT_LEAD: Talent leads → talentLeads Any objective bidding on plain impressions (no click/lead/conversion optimization goal) has Impressions as its result instead — cost per result there is just CPM. A Conversation/Message ad format has Message sends (sends) as its result regardless of objective. When reporting cost per result across campaigns with different objectives, resolve the field per campaign — never apply one field account-wide, and never collapse everything into a plain engagement-vs-conversion split. Array results are returned as a JSONL dataset, also readable via resources/read.

linkedin_list_entities

ChatGPT
Lists linkedin entities. searchCampaignGroups/searchCampaigns/searchCreatives accept a "search" criteria map to narrow the result, e.g. {"status": ["ACTIVE"]}. findAdSegmentsByAccount/findConversionsByAccount always list everything for this connection's own account instead. findAdSegmentsByAccount has its own separate "statuses"/"types" filter; findConversionsByAccount has its own separate "conversionOwnershipTypes" filter (include "SHARED" to also see conversions shared from other accounts in the same Business Manager). findLeadFormsByAccount lists this account's Lead Gen Forms — a LEAD_GENERATION creative's leadgenCallToAction needs one of their ids. findDmpSegmentsByAccount lists Matched Audiences (company/contact lists, retargeting and predictive audiences) — each one's destinations[0].destinationSegmentId is the Ad Segment campaigns target; findDmpSegmentListUploads/findDmpSegmentEngagementRules/findPredictiveAudiences take one segment's "dmpSegmentId", and findDmpEngagementTriggers an "engagementSourceType". findImagesByAssociatedAccount/findVideosByAssociatedAccount list the Media Library assets already uploaded to this connection's own account (optional "mediaLibraryStatus" filter) — check these before uploading a new asset, since an existing asset's id can be used directly as a creative.create content.media. Every operation here always returns its complete result set, up to a 20,000-row safety cap — pass "search" (or, for findAdSegmentsByAccount, "statuses"/"types") to stay under it on an account with more rows than that. For targeting lookups (resolving location/industry/title names to URNs), use linkedin_resolve_targeting_urns. ━━━ OPERATIONS ━━━ searchCampaignGroups: Search/list CampaignGroups. Searchable fields: id, status, name. searchCampaigns: Search/list Campaigns. Searchable fields: id, status, campaignGroup, name. searchCreatives: Search/list Creatives. Each searchable field is its own flat query param (a list of values), not wrapped in a "search" object. Searchable fields: campaigns, contentReferences, creatives, intendedStatuses. findAdSegmentsByAccount: List Ad Segments for one or more sponsored accounts, optionally filtered by statuses/types. findDmpSegmentsByAccount: List every DMP segment (company/contact list, retargeting, predictive audience) for this account. findDmpSegmentListUploads: List a list-upload DMP segment's attached CSV. Requires "dmpSegmentId". findDmpSegmentEngagementRules: List a retargeting DMP segment's engagement rules. Requires "dmpSegmentId". findPredictiveAudiences: List the predictive audiences in a DMP segment. Requires "dmpSegmentId". findDmpEngagementSourceTypes: List the engagement source types retargeting audiences can use, with their look-back bounds. findDmpEngagementTriggers: List the engagement triggers of one source type. Requires "engagementSourceType". findAdPageSetsByAccount: List this account's website page sets — the sources of website retargeting audiences. findConversionsByAccount: List every Conversion configured for a sponsored account. GET /rest/conversions?q=account&account={accountUrn}. Optionally add conversionOwnershipTypes=List(OWNED,SHARED) (202605+) to also return conversion rules shared across ad accounts under the same Business Manager (see the response "ownershipType" field) — omitted, only conversions owned by this account are returned. findLeadFormsByAccount: List every Lead Gen Form owned by this account. A form's id goes into a LEAD_GENERATION creative.create's leadgenCallToAction.destination as "urn:li:adForm:{id}" — use a PUBLISHED one. findImagesByAssociatedAccount: List every Image asset in this account's Media Library, most recently modified first. Each element's "id" (a urn:li:image:... URN) can be used directly as a creative.create operation's content.media — no re-upload needed. findVideosByAssociatedAccount: List every Video asset in this account's Media Library, most recently modified first. Each element's "id" (a urn:li:video:... URN) can be used directly as a creative.create operation's content.media — no re-upload needed. Arra…

linkedin_resolve_targeting_urns

ChatGPT
Resolve a human-readable targeting term (a location, industry, job title, seniority, skill, employer, etc.) to its facet/entity URN, or the reverse — never invent a URN or guess one from memory. Also doubles as the reverse (URN -> name) lookup: pass urns (an array of exact URNs) instead of query. To fetch several facets at once, pass facet as an array (e.g. ["seniorities", "genders"]) instead of a single string — query, if given, narrows every facet in the batch the same way. This ONLY works when every facet in the array is enumerated/static (marked "enumerated" below); query-driven facets (typeahead/warehouse-backed, e.g. titles, skills, locations, employers) have no bulk finder and must be resolved one at a time — call this tool once per facet with a single facet name. ━━━ SUPPORTED FACETS (short name -> recommended finder) ━━━ genders (enumerated — browsable without a query, "query" narrows it) jobFunctions (enumerated — browsable without a query, "query" narrows it) seniorities (enumerated — browsable without a query, "query" narrows it) ageRanges (enumerated — browsable without a query, "query" narrows it) staffCountRanges (enumerated — browsable without a query, "query" narrows it) interfaceLocales (enumerated — browsable without a query, "query" narrows it) yearsOfExperienceRanges (enumerated — browsable without a query, "query" narrows it) memberBehaviors (enumerated — browsable without a query, "query" narrows it) buyerGroups (enumerated — browsable without a query, "query" narrows it) companyCategory (enumerated — browsable without a query, "query" narrows it) growthRate (enumerated — browsable without a query, "query" narrows it) revenue (enumerated — browsable without a query, "query" narrows it) industries (unbounded - specify 'query' or 'urns' to use) locations (unbounded - specify 'query' or 'urns' to use) profileLocations (unbounded - specify 'query' or 'urns' to use) employers (unbounded - specify 'query' or 'urns' to use) employersPast (unbounded - specify 'query' or 'urns' to use) employersAll (unbounded - specify 'query' or 'urns' to use) groups (unbounded - specify 'query' or 'urns' to use) titles (unbounded - specify 'query' or 'urns' to use) titlesPast (unbounded - specify 'query' or 'urns' to use) titlesAll (unbounded - specify 'query' or 'urns' to use) skills (unbounded - specify 'query' or 'urns' to use) schools (unbounded - specify 'query' or 'urns' to use) followedCompanies (unbounded - specify 'query' or 'urns' to use) degrees (unbounded - specify 'query' or 'urns' to use) fieldsOfStudy (unbounded - specify 'query' or 'urns' to use) firstDegreeConnectionCompanies (unbounded - specify 'query' or 'urns' to use) audienceMatchingSegments (not resolved here — see findAdSegmentsByAccount via linkedin_get_entity_by_id) dynamicSegments (not resolved here — see findAdSegmentsByAccount via linkedin_get_entity_by_id) interests (unbounded - specify 'query' or 'urns' to use) Array results are returned as a JSONL dataset, also readable via resources/read.

linkedin_validate_bulk_mutate

ChatGPT
Validate a file of LinkedIn Ads mutations without changing anything. Always run this on the file before calling linkedin_bulk_mutate. Checks the file format, every field against linkedin_describe_mutation's schemas, immutable-field edits, optRefId: referential integrity (a tempId must be declared before it is referenced, and only a create resolves to a real id), and cross-entity rules. It reads the live account for every existing entity the file references by id, so those rules hold whether the referenced Campaign Group/Campaign/Creative is created earlier in the file or already exists: the id must exist in this account; a Campaign Group's objectiveType, status and bid strategy constrain its Campaigns; a Campaign's type, format and objective constrain its Creatives; an update is checked merged with the entity's current values; a delete's currentStatus must match the live status; budgets must be in the account's currency. linkedin_bulk_mutate runs these exact same checks itself before applying, so nothing slips through skipping this tool — it's a preview, not a gate. A file with problems fails with every problem named by line and the fix, all at once so a large file can be corrected in one pass — fix the file and re-validate as many times as you need, at no risk. A clean file returns changeSummary: every operation named against the live account, with its parents, current → new values and notes such as a paused parent, what goes live or a tenfold budget increase. changeDetailsFilePath is a JSON file beside the mutation file with the full per-operation detail; read it only when you need more than the summary shows. Check changeSummary for anything you did not intend, then tell the user what it will change — especially what goes live and what is removed — and apply only once they agree. MCP has no approval-card step, so this is the review. Absolute path of the JSONL mutation file inside the sandbox (e.g. /mnt/files/launch.jsonl). Write it with run_python or run_bash first. The first line declares the Opteo account id this file is written for: {"account": "li_123"} (the Opteo accountId, in the Account Context section of your instructions). Required. Then one operation per line, each exactly {"kind": ..., "tempId": ..., "payload": {...}}. - kind is <entityType>.<operation>, e.g. "campaign.create". Entities: campaignGroup, campaign, conversion, campaignConversionAssociation, creative, image, video, dmpSegment, dmpSegmentCompanies, dmpSegmentUsers, dmpSegmentListUpload, dmpSegmentEngagementRules, predictiveAudience, campaignAudiences, adPageSet — linkedin_describe_mutation lists every valid kind. Call linkedin_describe_mutation with a kind for its full field reference before writing lines for it. - tempId is a negative integer you choose to name this not-yet-created entity, unique within the file. - payload uses the same field names and shapes linkedin_describe_mutation documents for that kind. A later line references an earlier tempId by writing "optRefId:" followed by that same negative number in place of a real id's digits, e.g. "urn:li:sponsoredCampaignGroup:optRefId:-1". The declared account must be the account this tool is applying to — a mismatch refuses the whole file.

list_account_groups

ChatGPT
List account groups. Returns the account groups (typically, clients) configured on the user's Opteo team — each with its name, accountGroupId, and the account IDs assigned to it (team-wide, regardless of which team member is linked to each account). Fetch the remaining per-group settings with get_account_group_settings; create or modify a group with create_or_update_account_group.

list_accounts

ChatGPT
List accounts. Returns the advertising and analytics (GA4) accounts the user has explicitly linked to their Opteo team — the Opteo accountId, accountName, the platform-native platformId (Google CID, Meta account ID, Microsoft account number, etc.), the snake_case platform string, currency, timezone, linked status (isLinked), the assigned user (id, email, name) if one is set, the team users linked to the account (linkedUsers), and the accountGroupId of the account group it belongs to (null if unassigned; cross-reference list_account_groups for details). Use the returned accountIds when calling other tools. By default only linked accounts are returned; set includeInactive=true to also list every account visible via the connected platform tokens (which can run into the thousands and is rarely useful). For GA4 accounts (platform "ga4"), platformId is the GA4 property ID; query them with ga4_run_query / ga4_discover_fields. Array results are returned as a JSONL dataset, also readable via resources/read.

list_excluded_accounts

ChatGPT
List excluded accounts for this Opteo team. Excluded accounts are ignored when Opteo imports accounts into its systems, such that they never show up in Opteo features. Exclusions apply across all team members and all of the team's platform connections. Array results are returned as a JSONL dataset, also readable via resources/read.

list_improvements

ChatGPT
List Opteo improvement recommendations for the account, without their full details. An alternative to reading the same information from Signals. Improvements are automated, data-driven suggestions generated by Opteo to help optimise the account's ad campaigns. Each improvement identifies a specific problem or opportunity — for example, adjusting keyword bids that are too high or too low, pausing underperforming ads, adding negative keywords to cut wasted spend, or improving Quality Scores. Every improvement has a recommendedAction describing the specific action recommended (e.g. "adjust_keyword_bids", "pause_ad", "add_negative_keyword"), and an optional costSavings field showing the monthly spend the improvement affects (not strictly a saving) in the account's local currency. Active improvements are recommendations that have been identified but not yet acted on. They represent concrete, prioritised actions the account manager could take right now to improve performance. Completed improvements are recommendations that have already been applied ("pushed") by the account manager. They indicate optimisations that have already been made to the account. Dismissed improvements are recommendations that the account manager has explicitly deferred — they were removed from the active queue for a period of time (a week, a month, or indefinitely). Dismissed improvements cannot be pushed or dismissed again — they are view-only. If the user asks to act on a dismissed improvement, tell them they need to un-dismiss it from the Opteo UI first. Array results are returned as a JSONL dataset, also readable via resources/read.

list_issues

ChatGPT
List Opteo issues flagged for the account, without their full description. An alternative to reading the same information from Signals. Use this tool to discover which issues exist — it returns lightweight rows suitable for browsing, filtering, counting, and presenting summaries to the user. The full issue description is intentionally omitted to keep responses compact. Once you have identified specific issues you want to explain or act on, retrieve their description by fetching those issues by ID using fetch_issues. Issues flag things that are broken or require urgent attention in the account — for example, disapproved ads, empty campaigns or ad groups, broken landing page URLs, failed Smart Bidding learnings, or billing problems. They need to be resolved before the account can operate correctly. Active issues are problems currently affecting the account that have not yet been addressed. Completed issues have been resolved (pushed) by the account manager. Unresolved issues can directly suppress performance, so surface them prominently when analysing account health — for instance: "There are 2 disapproved ads that are not serving" or "A campaign has no active ads." Array results are returned as a JSONL dataset, also readable via resources/read.

list_saved_prompts

ChatGPT
List saved prompts. Returns the reusable chat prompts the user can run from the Opteo prompt library — their personal saved prompts plus their team's shared prompts in prompts, plus the full Opteo-curated library in opteoPrompts, available to everyone across all platforms. Both include full prompt text. Opteo-curated prompts are read-only; to customise one, create a personal or team copy with create_or_update_saved_prompt.

load_skill

ChatGPT
Load a skill — a vetted block of documentation or a multi-step procedure for analysing the user's account. Available skills (with their names and trigger conditions) are have been shown to you under "## Available Skills"; only call this tool with one of those names. When the skill is a multi-step procedure, follow the steps literally, only diverging from the instructions if the user explicitely asks you to, or if it is necessary to achieve the desired outcome. The tool returns the skill's procedure as markdown, plus a footer telling you where the skill's auxiliary files (sub-step references, helper scripts, datasets, etc.) are mounted on the sandbox. The procedure may reference those files by relative path (e.g. "see references/methodology.md"). No need to read everything up-front -- read them as you work through the skill. (Or not read at all, in the case of scripts, which can be executed directly). Read these files on demand with bash, eg: cat /mnt/files/skills/<name>/<path> To run scripts that import each other as a Python package, cd to the skill root first: cd /mnt/files/skills/<name>/ && python -m scripts.<module>

marketresearch_get_ad_library_ads

ChatGPT
Search public ad libraries for competitor ads by one or more keywords (queries) and/or advertisers. Each query and each advertiser is searched separately, in parallel, and the results are merged and de-duplicated. Support per platform — meta, linkedin: queries and/or advertisers; google: advertisers only (website domains like "nike.com"); tiktok: advertisers only. Returns ad creative (text, CTAs, media URLs, advertiser logos where available) and run dates. Media URLs point at permanent copies, so they keep working in reports. TikTok advertisers are looked up as TikTok accounts: rows are the account's most popular posts worldwide, with plays/likes/comments/shares/saves and promoted: true on posts boosted through TikTok Ads (organic posts are included too). Name lookups take TikTok's top account match — check tiktokAccounts and retry with the right "@handle" if it is the wrong brand. Array results are returned as a JSONL dataset, also readable via resources/read.

marketresearch_get_competitor_keywords

ChatGPT
Get the most successful paid-search keywords for one or more competitor domains using SpyFu. Array results are returned as a JSONL dataset, also readable via resources/read.

marketresearch_get_competitors

ChatGPT
Find the current account's top search competitors using SpyFu's combined PPC and SEO keyword-overlap data. The account's domain is resolved from its highest-spend ad destination. Array results are returned as a JSONL dataset, also readable via resources/read.

marketresearch_get_reddit_comments

ChatGPT
Read the comment threads of one or more Reddit posts. Returns one flat row per comment, keeping the reply structure in depth (1 = top-level comment, 2 = a reply to it) Array results are returned as a JSONL dataset, also readable via resources/read.

marketresearch_get_reddit_posts

ChatGPT
Search Reddit for posts by keyword, and/or browse a subreddit's feed. Each query and each subreddit is fetched separately, in parallel, and the results are merged and de-duplicated. One fetch per term: about 7 posts per query, about 25 per subreddit. Widen a thin result set with more queries. Returns the post title, body text, author, subreddit, score, comment count and permalink — pass those permalinks to marketresearch_get_reddit_comments to read the discussion. Array results are returned as a JSONL dataset, also readable via resources/read.

metaads_browse_assets

ChatGPT
Open an in-chat gallery of a Meta ad account's image and video library so the user can visually browse and pick one or more assets to build an ad from (up to 10 — e.g. several for a carousel or dynamic creative). Prefer this over metaads_list_ad_images whenever the client can render UI widgets — it shows the actual thumbnails inline instead of relying on markdown image links. Pass the Meta account's accountId (from get_account_info or list_accounts). When the user picks assets, read what they chose by calling the fetch_widget_output tool with the sessionId this tool returns — their image_hash (for images) or video_id (for videos) values come back in the order picked, for you to use in an adcreative create line of a mutation file (metaads_mutate_resources).

metaads_compute_image_crops

ChatGPT
Compute image_crops coordinates for a source image of the given dimensions. Returns the image_crops dict ready to pass directly to create_ad_creative or bulk_create_ad_creatives. For each crop key the result is the largest centered region that fits within the source image while matching the key's aspect ratio — equivalent to "Original" crop (no content is cut off beyond what the ratio requires).

metaads_estimate_audience_size

ChatGPT
Estimate audience size for targeting specifications using Meta's delivery_estimate API. This function provides comprehensive audience estimation for complex targeting combinations including demographics, geography, interests, and behaviors. It also maintains backwards compatibility for simple interest validation.

metaads_fetch_creative_performance

ChatGPT
Fetch your own Meta account's ad creatives WITH their performance, as a JSONL of per-asset rows ready to tag with bulk_tag_creatives. The creative-tagging counterpart to metaads_get_insights: use it to analyse performance BY creative attribute. Scope with object_id (a campaign or ad set ID) or omit it for the whole account; it pulls ALL ads in that scope (any delivery status) in a few batched calls, with performance over the last 90 days. It emits ONE ROW PER ASSET carrying: - the canonical creative fields bulk_tag_creatives reads: image_urls / video_url for the media and copy for the ad text, plus video_id on video rows (the shared video, so ads reusing one video roll up on it), - metrics: impressions, clicks, spend, ctr, plus actions, action_values, video_avg_time_watched_actions, and video thruplay/quartile-retention arrays (parse these in run_python for conversions, ROAS, and hold-time), - impression_share and metrics_grain: "asset" (real per-asset metrics, Dynamic Creative ads), "ad" (ad-level metrics attached to each asset — a multi-asset non-DCO ad shares them), or "none" (no delivery yet), - the creative's branded-content (partnership ad) fields, plus any extra AdCreative fields named in fields, repeated on each of the creative's rows. The coverage summary reports how many delivered ads have no retrievable media and are omitted from the rows. Disclose this when interpreting a report. From run_python via opteo_tools a call has a fixed time window, so on a large account it stops listing ads before the end, writes the rows it has, and returns nextCursor: call again with after set to it and a new outputFileName, then concatenate the files. No nextCursor means the listing is complete. Workflow: metaads_fetch_creative_performance(object_id?) → offloaded JSONL → bulk_tag_creatives(inputPath, outputColumns) → group tags by performance with run_python. No separate insights call or join needed — the metrics are already on the tagged rows. Array results are returned as a JSONL dataset, also readable via resources/read.

metaads_get_account_info

ChatGPT
Get detailed information about a specific ad account, including its connected conversion pixels/datasets (adspixels) — the source of promoted_object.pixel_id when building OFFSITE_CONVERSIONS ad sets.

metaads_get_account_pages

ChatGPT
List the Facebook Pages an ad account posts as, discovered through multi-source walking of ad-account-scoped edges (creatives, ads, campaigns). Each entry carries the page id, a url that opens the Page, and used_by (sample campaign / ad / creative names that reference it). Page names are not available (reading a Page we don't manage is blocked for this token) — identify a Page by opening its url. Pages with restricted access return {id, error} blobs surfacing Meta's real permission error (e.g. pages_read_engagement missing) — surface those to the user as-is. When creating an ad on an account with more than one Page, show the user this list and let them choose which Page to publish as — never pick for them. Array results are returned as a JSONL dataset, also readable via resources/read.

metaads_get_ad_creatives

ChatGPT
Get creative details (copy, media, links, branded-content / partnership-ad fields) for one or more ads. Pass an array of ad_ids (1–25); the tool fans out to Meta in parallel and returns a flat list of creatives, each row tagged with its source ad_id, plus a sibling errors array surfacing per-ad failures (e.g. one ad's creative was deleted; the call still returns successful results for the other ads). Always prefer this over calling a per-ad creative tool 25 times. Use metaads_get_ads first to find ad IDs. Pass fields to add any other AdCreative field to each row. Array results are returned as a JSONL dataset, also readable via resources/read.

metaads_get_ad_details

ChatGPT
Get detailed information about a specific ad, with its creative's id and video_id. Pass fields to add any other Ad field.

metaads_get_ad_video

ChatGPT
Get video details and source URL for a Meta ad video creative. Returns the video source URL (direct download link), thumbnail URL, processing status, and metadata (title, description, duration). video_status is "processing" while Meta is still transcoding this video and "ready" once real frames are available. When it's "processing", thumbnail_url may be a generic placeholder rather than a frame from the video — surface that to the user instead of treating the thumbnail as final. Provide either ad_id (to auto-extract the video from the ad creative) or video_id directly.

metaads_get_ads

ChatGPT
Get ads for a Meta Ads account, filterable by ad set / campaign id. Each ad row has identifying info + an opaque creative reference (use metaads_get_ad_creatives for creative content, batched 1-25 ad_ids per call). Use metaads_get_ad_details for full per-ad config; use metaads_get_insights for performance metrics. Pass fields to add any other Ad field to each row. Array results are returned as a JSONL dataset, also readable via resources/read.

metaads_get_adset_details

ChatGPT
Get detailed information about a specific ad set.

metaads_get_adsets

ChatGPT
Get ad sets for a Meta Ads account, filterable by campaign id. Each ad set carries identifying info, optimization_goal, billing_event, bid_strategy, schedule, and other config from the default ADSET_FIELDS constant. Use metaads_get_adset_details for full per-ad-set targeting/bidding config; use metaads_get_insights for performance metrics. Array results are returned as a JSONL dataset, also readable via resources/read.

metaads_get_campaign_details

ChatGPT
Get detailed configuration for a single campaign (objective, status, effective_status, budgets, spend_cap, bid_strategy, buying_type, special ad categories). Includes a derived budget_level — "campaign" (CBO / Advantage+ Campaign Budget) or "adset" (ABO, where the budget lives on each ad set — see metaads_get_adsets).

metaads_get_campaigns

ChatGPT
Get campaigns for a Meta Ads account with optional filtering. Each row carries id, name, objective, status, effective_status, daily_budget, lifetime_budget, spend_cap (lifetime cap, minor units; absent when none is set), buying_type, start_time, stop_time, created_time, updated_time, bid_strategy, special_ad_categories, budget_remaining, and a derived budget_level — "campaign" (CBO / Advantage+ Campaign Budget) or "adset" (ABO, where the budget lives on each ad set — see metaads_get_adsets). Use metaads_get_campaign_details for additional per-campaign config; use metaads_get_insights for performance metrics (impressions, clicks, spend, conversions). Array results are returned as a JSONL dataset, also readable via resources/read.

metaads_get_creative_details

ChatGPT
Get detailed information about a specific ad creative by its ID, including its branded-content (partnership ad) fields. Pass fields to add any other AdCreative field.

metaads_get_insights

ChatGPT
Get performance insights for a campaign, ad set, ad or account. This is Meta's analog of run_gaql_query. Row granularity is controlled by level (enum: account, campaign, adset, ad), independent of object_id. E.g. object_id: <campaign_id> + level: "ad" returns one row per ad in that campaign; level: "campaign" collapses it to a single total row. object_id MUST be one id, not a comma-separated list. To query multiple objects, either broaden the level and filter client-side after the call, or call this tool once per id. There is no server-side multi-id filter. Use fromDate / toDate (ISO YYYY-MM-DD), not Meta's native time_range/date_preset. Note on response size: the tool always returns a fixed set of fields (impressions, clicks, spend, conversions via actions[]/action_values[], CTR, CPC, CPM, frequency, reach, etc.) and cannot filter to a subset. For large result sets (50+ rows), the actions/action_values arrays can make responses very large (1–2MB+); if you only need specific metrics like spend or impressions, set compact=true. Historical lookback caps: 37 months default; 13 months when most breakdowns are applied; 13 months for hourly breakdowns; 6 months for frequency_value. The API returns empty data (NOT an error) past the cap — an empty result with no error is a likely silent-cap symptom; validate fromDate/toDate before dispatch and never treat empty results as "zero performance". Array results are returned as a JSONL dataset, also readable via resources/read.

metaads_get_interest_suggestions

ChatGPT
Get interest suggestions based on existing interests. Returns suggested interests with id, name, audience_size, and description fields. Array results are returned as a JSONL dataset, also readable via resources/read.

metaads_list_ad_images

ChatGPT
List images in a Meta Ads account's image library, most recent first, so you can help the user pick one to build an ad from. Use this to find an image the user has just uploaded to their Meta account. The user needs to see the images to choose — do NOT just list filenames. If the metaads_browse_assets gallery widget is available, prefer it (it renders thumbnails reliably inline). Otherwise present an inline gallery: embed each thumbnail with markdown alongside its name: ![{name}]({preview_url}). (This markdown embed is a deliberate exception to the default "no embed" rule — but note some clients won't auto-load external image URLs, so the widget is better when offered.) Each row carries two image URLs for two different jobs. preview_url is a 128px thumbnail: use it ONLY for the picker gallery above — never in a report, artifact, or anywhere the image stands in for the creative. url is the full-size original (widthxheight) and is the one to use everywhere else; it is a temporary Meta URL, so download it with metaads_get_ad_image rather than linking it in anything the user keeps. Neither URL is valid input to ad creation — to build a creative from a chosen image, pass that image's hash as image_hash in an adcreative create line of a mutation file (metaads_mutate_resources). in_use: false means the image is not yet attached to any creative; a freshly-uploaded image is normally the top row with in_use: false. If the list is empty, ask the user to upload the image to their Meta Ads account's image library first.

metaads_preview

ChatGPT
Create a shareable link to an interactive Meta ad preview page. Previews use native Facebook iframes, including video and carousel interactions. Always show the returned URL as a markdown link. The link needs no Opteo login and stays valid for about 30 days, so it can be shared with teammates or clients.

metaads_search_behaviors

ChatGPT
Get all available behavior targeting options. Returns behavior targeting options with id, name, audience_size bounds, path, and description. Array results are returned as a JSONL dataset, also readable via resources/read.

metaads_search_demographics

ChatGPT
Get demographic targeting options. Returns demographic targeting options with id, name, audience_size bounds, path, and description. Array results are returned as a JSONL dataset, also readable via resources/read.

metaads_search_geo_locations

ChatGPT
Search for geographic targeting locations. Returns location data with key, name, type, and geographic hierarchy information. Array results are returned as a JSONL dataset, also readable via resources/read.

metaads_search_interests

ChatGPT
Search for interest targeting options by keyword. Returns interest data with id, name, audience_size, and path fields. Array results are returned as a JSONL dataset, also readable via resources/read.

metaads_search_pages_by_name

ChatGPT
Search for pages by name within an account. Array results are returned as a JSONL dataset, also readable via resources/read.

microsoftads_describe_bulk_entities

ChatGPT
Returns the full list of attribute fields available in the Microsoft Ads Bulk API for one or more entity types, with a description of each field. Call this before microsoftads_run_bulk_fetch or microsoftads_run_bulk_mutate when you need to know what fields a particular entity type has - for example to decide which entity type to request, to understand what a specific field means, or to discover the exact column names and Add/Update/Delete semantics needed to build a bulk upload row. Batch every distinct entity type a request will touch into one call.

microsoftads_describe_report

ChatGPT
Returns the full column list, filter fields, and aggregation options for a Microsoft Ads report type. Call this before microsoftads_get_metrics to discover which columns and filters are available for the report you want to run.

microsoftads_get_location_criterions_by_name

ChatGPT
Resolves location names or ISO codes to Microsoft Ads location IDs. Call this before adding or removing a location criterion (a "Campaign Location Criterion" / "Ad Group Location Criterion" bulk row, or their negative/exclusion variants) whenever you need a LocationId for a named place. Do NOT call GetGeoLocationsFileUrl — use this tool instead. ━━━ RETURN FORMAT ━━━ { countries: Record<string, number> // "germany" → 52131, "de" → 52131 states: Record<string, number> // "california" → 123456, "ca" → 123456 (US only) cities: Record<string, number> // "berlin" → 789012, "berlin, berlin" → 789012 unmatched: string[] // requested names that didn't resolve to an ID } Only entries matching a requested name are returned — pass every place you need resolved in one call rather than calling this once per name. Country entries match on both the ISO 2-letter code and common English names. State entries match on both the 2-letter code and full name (US only). If a bare city name is ambiguous across states, every "city, state" match is returned so you can disambiguate; check unmatched for any name that had no match at all. ━━━ AFTER RESOLVING THE ID ━━━ A location target/exclusion is a criterion, NOT a keyword — never build a Keyword row for a "target"/"exclude location" request. Before adding, fetch the campaign's or ad group's existing location criterions via microsoftads_run_bulk_fetch (entities: ["CampaignTargetCriterions"] and/or ["AdGroupTargetCriterions"], then filter the returned rows by their Type column) so you don't add a duplicate or fight an existing opposite-direction criterion. Then call microsoftads_run_bulk_mutate with a row of Type "Campaign Location Criterion" / "Ad Group Location Criterion" (target) or "Campaign Negative Location Criterion" / "Ad Group Negative Location Criterion" (exclude), with the Target column set to the resolved ID. See the ms-location-targeting skill for the exact column shapes at both campaign and ad-group level.

microsoftads_get_metrics

ChatGPT
Run a Microsoft Ads Reporting API report and return the results as structured data. Pass a reportType from the list below and a columns array. Call microsoftads_describe_report first to get the full column list and filter options for any report type before calling this tool. Note: Some report types segment rows below the level you asked for — e.g. one row per ad group even though you only requested campaign-level columns. Before grouping/summing by CampaignId (or any other column), check whether that key actually repeats in the results; if it does, aggregate the rows yourself rather than assuming one row per key. ━━━ AVAILABLE REPORT TYPES ━━━ AccountPerformanceReport Defines an account performance report request. Use this report to observe long-term account performance and trends. You can request impressions, impression share (%), clicks, spend, and average cost per click for individual accounts. Once downloaded, this data can be sorted by individual accounts, currency, bid match type, and delivered match type. AdDynamicTextPerformanceReport Defines an ad dynamic text performance report request. Use this report to identify which dynamic text strings are performing well and which strings you should consider changing. AdExtensionByAdReport Defines an ad extension by ad report request. Use this report to compare how well different versions of your ad extensions are performing with each ad. AdExtensionByKeywordReport Defines an ad extension by keyword report request. Use this report to compare how well different versions of your ad extensions are performing for each keyword. AdExtensionDetailReport Defines an ad extension detail report request. Use this report to discover the effectiveness of individual ad extension items, for example, each link of a sitelink extension. AdGroupPerformanceReport Defines an ad group performance report request. Use this report to more broadly compare delivery performance statistics by ad group, campaign, or account attributes rather than at the keyword level. AdPerformanceReport Defines an ad performance report request. Use this report to help you determine which ads lead to clicks and conversions, and which are not performing. Having underperforming ads in your account can pull down the quality of your campaigns. AgeGenderAudienceReport Defines an age and gender audience report request. Use this report to discover how your campaigns and ad groups are resonating with different age groups and genders. AppsPerformanceReport Defines an app campaign performance report request. AssetGroupPerformanceReport Defines a data object for AssetGroupPerformanceReportRequest. AssetPerformanceReport Defines an asset performance report request. AudiencePerformanceReport Defines an audience performance report request. Use this report to compare delivery performance statistics by audience, ad group, campaign, or account attributes. BidStrategyReport Defines a bid strategy report request. BudgetSummaryReport [no aggregation] Defines a budget summary report request. Use this report to discover how your campaign's budget is performing for the month. For example, the report shows your monthly budget, how much you have spent to date, and whether you are on target to spend your monthly budget. CallDetailReport Defines a call detail report request. Use this report to discover which accounts, campaigns, or ad groups are driving the most completed phone calls. CampaignPerformanceReport Defines a campaign performance report request. Use this report to view high-level performance statistics and quality attributes for each campaign or account. This is also a quick way to flag any major campaign or account problems. CombinationPerformanceReport Defines a combination performance report request. ConversionPerformanceReport Defines a conversion performance report request. Use this report to understand which campaigns and keywords are leading an audience to complete a purchase or other conversion action. DestinationUrlPerformanceReport Defines a destination URL performance report request. Use this report to identify landing page…

microsoftads_run_bulk_fetch

ChatGPT
Download entity data from the Microsoft Ads Bulk API. Returns all records of each requested entity type across the entire account. Use this whenever you need campaign-structure or entity-level settings (status, bid strategy, budget, criterions, and so on) rather than performance metrics — for example: - All records of a type across the account (e.g. all keywords in the account) - Mapping the full account structure in one pass When you know which campaigns you need - a lookup of specific entities, or everything in a few campaigns - use microsoftads_run_bulk_fetch_by_campaign_ids instead: on a large account it takes seconds instead of a minute. Use microsoftads_describe_bulk_entities to discover what fields a specific entity type returns before fetching, especially for unfamiliar entity types. Use warehouse-backed fetch tools (multiplatform_fetch_campaigns, fetch_keywords, etc.) when you need performance metrics. NOTE: Bulk downloads typically take 10–60s. Only request the entity types you actually need. Array results are returned as a JSONL dataset, also readable via resources/read.

microsoftads_run_bulk_fetch_by_campaign_ids

ChatGPT
Download entity data from the Microsoft Ads Bulk API for specific campaigns only - the same records microsoftads_run_bulk_fetch returns, limited to those campaigns. Prefer it over microsoftads_run_bulk_fetch whenever you know which campaigns you need: on a large account it takes seconds instead of a minute. Campaign-owned records (campaigns, ad groups, keywords, ads, criterions, negative keywords, label associations) come back for the requested campaigns only. Account-level libraries come back whole regardless: Bid Strategies, Budgets, Labels, Negative Keyword Lists and Shared Negative Keywords. Ad extensions come back only where associated with the requested campaigns, and Feeds / Feed Items aren't returned at all - use microsoftads_run_bulk_fetch for those. An Id that isn't a campaign in this account is skipped without an error. Array results are returned as a JSONL dataset, also readable via resources/read.

microsoftads_run_bulk_mutate

ChatGPT
Create, update, or delete Microsoft Ads entities in bulk via a single Bulk API CSV upload (GetBulkUploadUrl -> upload -> GetBulkUploadStatus poll -> result file). Can create/update/delete any mix of entities - campaigns, ad groups, keywords, ads, criterions, and more - in one request. This is the only tool for creating, updating, or deleting Microsoft Ads entities. Rows are a CSV file in the sandbox, not tool arguments - see filePath below. Type Hierarchy The Format Version record is required and must precede every other record in the file (added automatically by this tool - do not include it yourself). - If a parent entity is created in the same request, it must precede any dependent child records. For example, when associating a sitelink ad extension with a campaign, the Campaign Sitelink Ad Extension record must come after both the Campaign and Sitelink Ad Extension records. The Id and Parent Id fields of the Campaign Sitelink Ad Extension record should be set to the identifier of the Sitelink Ad Extension and Campaign records respectively. If those parent records are also new and do not yet have assigned Microsoft Advertising identifiers, use Reference Keys (below). - It is not required to include the record for a parent entity that already has a valid Microsoft Advertising identifier. - Partial success is supported when adding, updating, and deleting records. For example if you try to add three campaigns and only two are correctly specified, two will be added - the result rows will include details for both successful Campaign records, the one attempted Campaign record, and one Campaign Error record. - If a new campaign's identifier is not yet known - for example when adding a campaign, ad group, ad, and keyword in the same request - give each new parent a negative Id and set it as the Parent Id of its child records (see Reference Keys below). It is not necessary to specify an existing parent in the upload. - Partial updates are supported for records including negative keywords, negative sites, and target criterions. For example you can update the bid of a single location criterion without re-uploading the entire set of target criterions for the campaign or ad group. - When updating a record, the Id field is required. The Parent Id or Reference Keys to the parent record is also required. - When updating a campaign or ad group name, it is optional to specify the new name for child records if the correct Parent Id is provided. - To replace one set of records with another, specify the deleted records prior to the new set. For example to replace all existing Campaign Negative Keyword records for a campaign, first include a Campaign Negative Keyword row with Status set to Deleted and Parent Id set to the campaign ID and no Id (omitting Id there deletes every Campaign Negative Keyword for that campaign). Below that, include one or more new Campaign Negative Keyword rows with all required properties for the add. - To replace an existing record with a new record that has the same unique properties, specify the deleted record prior to the new one. For example to replace an Ad Group Dynamic Search Ad Target, first include a row with Status set to Deleted, Id set to the existing target's ID, and Parent Id set to the ad group ID; below that, include the new row. In most cases you can update the existing record instead of a delete+add pair - for example updating the Bid Adjustment of an existing Campaign Gender Criterion. - When deleting a record, the Id field is required, and a reference to the parent entity (a system identifier or Reference Key) is also required. For example when deleting an ad group, its Parent Id must match the Id of the Campaign record. - With a few exceptions the result rows only include the columns you uploaded - for example if you add a new Ad Group Negative Keyword without an Id column, the result will not include its assigned identifier. The exceptions are campaigns, ad groups, ads, and keywords, which always get every column back regardless of what was uploaded. Update with delete_value To remove…

microsoftads_validate_bulk_mutate

ChatGPT
Validates a microsoftads_run_bulk_mutate file without changing anything. Always run this on the file before calling microsoftads_run_bulk_mutate. Checks against the account's live campaigns, ad groups and bid strategies the file touches, downloading just those - typically a few seconds. A file with problems fails with every issue named by row, column and reason - fix the file and re-validate as many times as you need, at no risk. A clean file returns changeSummary: every row named against the live account, with its parents, current → new values and notes such as a paused parent or a tenfold bid increase. Check it for anything you did not intend before running the file. changeDetailsFilePath is a JSON file beside the bulk file with the full per-row detail; read it only when you need more than the summary shows.

multiplatform_fetch_account

ChatGPT
Designed for cross-platform analysis. For single-platform queries that need fresh data, prefer the native platform tool (googleads_run_gaql_query, metaads_get_campaigns, etc.) Get account-level performance metrics including: impressions, clicks, cost, conversions, conversion value, CTR, CPC, CPA, ROAS, and conversion rate. Can be called several times with different date ranges to emulate "vs previous period" functionality. Use segment to break results down by day, week, or month. History goes back about 3 years; dates before that return no data. Data is refreshed every 24h, so do not use this tool for live data; use the native platform tool (googleads_run_gaql_query, metaads_get_*) for that. Array results are returned as a JSONL dataset, also readable via resources/read.

multiplatform_fetch_campaigns

ChatGPT
Designed for cross-platform analysis. For single-platform queries that need fresh data, prefer the native platform tool (googleads_run_gaql_query, metaads_get_campaigns, etc.) Get campaigns with performance metrics including: impressions, clicks, cost, conversions, conversion value, CTR, CPC, CPA, ROAS, and conversion rate. Can be called several times with different date ranges to compare performance across periods. Use segment to break results down by day, week, or month — each campaign will have one row per period. History goes back about 3 years. Dates before that return no data. Data is refreshed every 24h, so do not use this tool for live data; use the native platform tool (googleads_run_gaql_query, metaads_get_*) for that. Array results are returned as a JSONL dataset, also readable via resources/read.

multiplatform_fetch_change_history_details

ChatGPT
Get detailed individual change records from an account's change history / audit log. Returns each change with: entity IDs, human-readable summaries of what changed (e.g., "status: PAUSED -> ENABLED", "daily_budget: $50.00 -> $100.00"), and best-effort campaign/ad-group name/type/status. Name and status fields may be null for older or removed entities that are no longer in the active attribute tables — entity IDs and the change summary remain reliable in those cases. Supports filtering by campaign type (e.g., PERFORMANCE_MAX, SEARCH) to answer questions about specific campaign types. Use this tool directly when the user asks about specific entities, campaign types, or what exactly changed. If truncated=true, tell the user the total count and suggest narrowing filters or date range. IMPORTANT: Change history data is accumulated over time via periodic syncs (Google: 30-day rolling window, Microsoft: 7-day rolling window). The dataRange.earliest field in the response indicates the oldest recorded change — any changes that occurred BEFORE that date are simply unrecorded, NOT absent. If the user asks about a period before dataRange.earliest, explicitly tell them that change history data is only available from that date onward and earlier changes were not captured. Opteo's warehouse retains longer change-history than the native platform APIs expose, so this is the right tool for historical audits even on a single platform. Data is refreshed every 24h, so do not use this tool for live data; use the native platform tool (googleads_run_gaql_query, metaads_get_*) for that. Array results are returned as a JSONL dataset, also readable via resources/read.

multiplatform_fetch_change_history_summary

ChatGPT
Get a daily summary of changes made to an advertising account. Returns aggregated daily counts grouped by date, resource type, and operation — useful for spotting patterns like "50 keyword changes on Tuesday" or "budget changes right before a spend increase". Does NOT return individual change details or campaign names. If the user asks about specific campaigns, campaign types (e.g., Performance Max), or wants to know exactly what changed, use multiplatform_fetch_change_history_details instead — it returns full per-change records with names, types, and diffs. Includes 7 days of lead-in context before the requested date range. IMPORTANT: Change history data is accumulated over time via periodic syncs (Google: 30-day rolling window, Microsoft: 7-day rolling window). The dataRange.earliest field in the response indicates the oldest recorded change — any changes that occurred BEFORE that date are simply unrecorded, NOT absent. If the user asks about a period before dataRange.earliest, explicitly tell them that change history data is only available from that date onward and earlier changes were not captured. Opteo's warehouse retains longer change-history than the native platform APIs expose, so this is the right tool for historical audits even on a single platform. Data is refreshed every 24h, so do not use this tool for live data; use the native platform tool (googleads_run_gaql_query, metaads_get_*) for that. Array results are returned as a JSONL dataset, also readable via resources/read.

multiplatform_fetch_conversions

ChatGPT
Designed for cross-platform analysis. For single-platform queries that need fresh data, prefer the native platform tool (googleads_run_gaql_query, metaads_get_campaigns, etc.) Get available conversion actions for the account including their IDs, names, categories, primary status, and total conversion counts over a recent lookback period. Use this to discover conversion action IDs that can be passed as conversionIds to other fetch tools. Data is refreshed every 24h, so do not use this tool for live data; use the native platform tool (googleads_run_gaql_query, metaads_get_*) for that. Array results are returned as a JSONL dataset, also readable via resources/read.

multiplatform_fetch_shared_negative_lists

ChatGPT
Fetch shared negative keyword lists for the account, with campaign and keyword counts. Array results are returned as a JSONL dataset, also readable via resources/read.

notify_via_email

ChatGPT
Notify the user — or a member of their Opteo team — by email. For delivering results the user asked to receive by email, such as the outcome of a scheduled task; not a general-purpose email client. Don't email unprompted. An invalid address returns the list of valid ones.

notify_via_slack

ChatGPT
Notify the user's team in a Slack channel connected to Opteo. For delivering results the user asked to receive in Slack, such as the outcome of a scheduled task; not a general-purpose messaging tool. Don't post unprompted. Write standard Markdown. Files can't be attached — publish the file and include its link. Available channels are the team's default Slack connection plus any per-account connections on accounts the user can access. With one channel, omit channel. With several, call without it to get the list, then pick the channel that fits (e.g. the account-specific one for a report about that account).

push_improvements

ChatGPT
Push (apply) one or more active Opteo improvements to the account. This executes the same action as clicking "Push" in the Opteo improvements UI — it applies the recommended change directly to the ad account via the Google Ads API (or relevant platform API). STOP — check extraDetailsDescription before calling this tool Before pushing an improvement, call fetch_improvements and check the extraDetailsDescription field on each improvement you are about to push. - If extraDetailsDescription is non-empty: read it to understand what to ask the user, then ask in a regular reply (not a tool call). Wait for the user's answer. Only then call this tool with the collected choices in the details field. - If it starts with [REQUIRED]: do not call this tool until the user has answered — it will throw or fail otherwise. - If it starts with [OPTIONAL]: ask the user but proceed with defaults if they don't specify (omit the details field). - If extraDetailsDescription is null or empty: push immediately, no details needed. - If extraDetailsDescription says the improvement cannot be pushed via chat: tell the user to push it from the Opteo UI instead. Do not attempt to push it. Only push improvements that the user has explicitly asked to apply. Always present the account's active improvements to the user (from its improvement signals or list_improvements) before pushing. Confirming before pushing Before calling this tool, always tell the user exactly what you are about to do. When the action involves a specific entity (ad group, campaign, shared list, etc.), always state both the name and the ID, e.g. "I'll add [keyword] as EXACT in ad group "Ad Group Name" (123456789) in campaign "Campaign Name" (987654321)." Never refer to entities by ID alone or with a vague description like "the suggested ad group". Calling the tool Pass each improvement as an entry in the improvements array. Include a details object only for improvements where the user has made a specific choice. Omit details for improvements using default behaviour. Returns a result per improvement indicating whether it succeeded or failed.

refresh_available_accounts

ChatGPT
Refresh available ad accounts. Re-syncs the account list each team member's platform connections can see (per ad platform) into Opteo. Use when an account is missing from list_accounts or set_account_link_status reports no relationship row — e.g. an account recently added to an MCC / Business Manager that Opteo has not picked up yet. Syncs run for the whole team and can take a while on large MCCs. Returns a per-platform synced/error status; call list_accounts (includeInactive: true) afterwards to see the refreshed account list. GA4 is not syncable here — GA4 properties are team-connected, not per-user.

regroup_account_campaign_groups

ChatGPT
Rebuild an account's campaign groups (aka Targets) with AI ("smarter reset"): re-segments the active campaigns per channel and recreates all non-EXCLUDED groups; on Google/Microsoft, brand-themed search campaigns are routed to the Brand group. Destructive and not undoable — it wipes the existing campaign groups' target settings, so only call it when the user has explicitly asked to regroup or reset that account. The EXCLUDED group and its campaigns are preserved. Any account-level opteo conversion action settings are also left untouched. Returns the resulting campaign groups per account.

resolve_issues

ChatGPT
Resolve active Opteo issues by rechecking them (after the user fixes the problem) or dismissing them. Issues are problems that require the user to fix the underlying cause themselves — unlike improvements, they cannot be applied automatically by Opteo. Resolution workflow (follow this exactly): 1. Use list_issues to get active issues, then use fetch_issues to retrieve full descriptions for the specific issues you need 2. For each issue, explain in plain language what is wrong, why it matters, and exactly what the user needs to do to fix it — use the "description" field from fetch_issues as your guide 3. Work through issues one at a time — present one issue, wait for the user to respond, then move to the next 4. After explaining an issue, ask: "Have you fixed this, or would you like to dismiss it?" - If the user confirms they've fixed it: call this tool with action "recheck" - If the user wants to skip or ignore it: call this tool with action "dismiss" 5. Never call recheck speculatively — only after the user explicitly confirms they have resolved the underlying problem 6. Never dismiss without clear user instruction to do so recheck — triggers Opteo to re-verify whether the issue still exists (same as clicking "Recheck" in the Opteo UI). The check runs asynchronously in the background; the issue status will update automatically. dismiss — permanently removes the issue from the active queue (same as clicking "Ignore Issue" in the Opteo UI). Returns a result per issue indicating whether the action succeeded or failed.

run_bash

ChatGPT
Execute a bash command in the same sandbox container as run_python. Use for file inspection, transformation, and quick aggregations where a one-liner beats spinning up a pandas DataFrame. Filesystem /mnt/files/ is shared with run_python — write a CSV in Python, then process it here with wc -l, head, jq, grep, awk and pipes. User-attached files are also available there. Examples: ``bash wc -l /mnt/files/*.jsonl cat /mnt/files/fetchCampaigns_*.jsonl | head -5 jq -r '.status' /mnt/files/keywords.jsonl | sort | uniq -c ` Network The sandbox has internet access — curl/wget work, e.g. to download a file a tool returned as a public URL into /mnt/files/. Calling Opteo tools import opteo_tools works from any python subprocess you launch here (e.g. python3 script.py), exactly as it does in run_python — see the run_python description. Shell state Each call is a fresh bash -l -c exec; no shell state (cwd, env, variables) carries between calls — chain commands with && / ;` inside one invocation if needed. stdout and stderr are each capped at 10000 characters; past that the output is cut and a marker reports how many chars were hidden.

run_python

ChatGPT
Execute Python code in a persistent sandbox with pandas and numpy (and other common data science libraries) pre-installed. Use for general data analysis and for running the scripts specified in skills loaded via load_skill. Data files Data files from tools and user attachments live under /mnt/files/. Skill files mounted via load_skill are at /mnt/files/skills/<name>/. Tool responses include a sandbox-local filePath; load it directly: ``python import opteo_tools df = opteo_tools.load_df(file_path) ` Careful using pd.read_json directly — it silently rounds long numeric-string ids (Meta/Google ids of 16+ digits) to float64. Network The sandbox has internet access: fetch URLs with requests/urllib (e.g. download a file a tool returned as a public URL into /mnt/files/), and pip install extra packages if one is missing. Calling Opteo tools from Python import opteo_tools — most read-only Opteo tools are available as a function with the same name and input schema as the regular tool, e.g. opteo_tools.fetch_platform_data(query=['...']). Returns the same result a direct tool call would produce (parsed JSON, or raw text for tools that return text); large results are offloaded to /mnt/files/.jsonl` as usual — load the returned `filePath` with `opteo_tools.load_df`. Tool errors raise `opteo_tools.OpteoToolError`. Opteo tool calls inside this Python code automatically use the current sandbox; omit `sandboxId` from those calls. Use this to loop, batch, or join multiple tool calls in one script instead of several separate tool calls. `opteo_tools.call_tool(name, *kwargs) calls by name. State persistence The sandbox's state is persistent. Every call to this tool is equivalent to running the code in the same Python interpreter, so you can re-use imports, files, variables, functions, etc across calls. Never reconstruct data by typing values inline based on what you remember from prior tool output text — that produces fabricated results. Re-construct from persisted ground-truth. Working with numbers Never compute aggregations, sums, averages, ratios, sorting, comparisons, projections, or anything else over more than a tiny amount of rows in your head — always use the sandbox. Mental math produces subtle errors that users notice immediately. If a number can be computed from data in /mnt/files/`, compute it there. Print results to stdout. Both stdout and stderr are capped at 10000 characters each; past that the output is cut and a marker reports how many chars were hidden.

search_web

ChatGPT
Search the web and returns the 10 most relevant results.

set_account_exclusion_status

ChatGPT
Set account exclusion status. Saves team-wide import exclusions and re-syncs the affected platforms. Excluding accounts prevents them from being imported into Opteo, removing them all features. If they were already imported, they will be automatically unlinked, and unable to be re-linked until un-excluded.Allowing import makes accessible accounts available again under normal sync behavior. Use list_excluded_accounts to find exclusions.

set_account_settings

ChatGPT
Set account settings. Applies any subset of per-account Opteo configuration to the given accounts — budget amount, performance mode, enabled conversion actions, onboarding-wizard visibility, performance goals, description (target-market statement), brand terms, competitors, industry, alert muting, disabled improvement types, structure overview, and the assigned user. Unset fields are left unchanged. Description, brand terms, competitors, and industry override the account group's equivalents (create_or_update_account_group) when set. The write counterpart of get_account_settings — call that first for current values and valid identifiers. Returns per-account results: updated and failed section names (with reasons); sections are applied independently, so one failure does not roll back the others.

set_user_tool_permissions

ChatGPT
Set User Tool Permissions for a member of your team. Only team admins can call this operation. Use the member’s Opteo user ID (linkedUsers from list_accounts includes IDs) and canonical capability names. The toolName enum in this schema lists every configurable tool and bulk-operation permission across all platforms. Bulk wrappers have no separate permission. Only specified permissions change. New capabilities default to allowed. These settings apply across Opteo chat and MCP.

share_file

ChatGPT
Share a file from the sandbox with the MCP client by registering it as a fetchable MCP resource. Use this when you have generated a file inside run_python (CSV, JSON, PNG, etc.) and you want the user to be able to download it from their MCP client. The file is copied out of the sandbox, registered with a 7-day TTL, and returned as a resource_link.

submit_widget_output

ChatGPT
Internal: called by an in-chat widget to hand the result of what the user did to the assistant. Not for direct use.

tiktokads_bulk_mutate

ChatGPT
Apply a file of TikTok Ads mutations, all in one call, correctly ordered by dependency. This is the only TikTok Ads mutation tool. It covers: - Manual Campaigns, Ad Groups and Ads: create, update, status and budget changes. - Upgraded Smart+ Campaigns, Ad Groups and Ads, including enabling or disabling individual Smart+ creatives. - GMV Max Campaigns (Product and LIVE): create and update. - Reach & Frequency ad groups: book, update, and withdraw the booking. - Smart Creative ads: create and update their materials, and enable or disable a material. - Customer-file custom audiences built from emails, phone numbers or mobile advertising ids (Opteo hashes them), adding to, removing from or replacing their contacts. - Rule-based and lookalike custom audiences, Saved Audiences, Offline Event Sets, and image/video creative assets; authorizing a creator's post for Spark Ads with their authorization code. It cannot create Instant Forms or legacy Smart+ campaigns. Give each operation a negative tempId; any other operation in the same file that needs to reference it (before it is real) writes "optRefId:" followed by that same negative number where a real id would go, e.g. "optRefId:-1". Call tiktokads_describe_mutation with the operation kind (e.g. "campaign.create") for its full payload field reference. Validates the file itself before applying — the same checks tiktokads_validate_bulk_mutate runs — and rejects it, naming every problem, if anything is wrong. Call tiktokads_validate_bulk_mutate on the file first anyway: fixing issues there, before the user sees this tool's approval card, is cheaper than fixing them after. Every operation targets exactly one entity and is all or nothing — to change several campaigns, ad groups, or ads, write one operation per entity. Returns one result per operation, in file order, as { tempId, isSuccess, resultData?, error? } — an operation whose dependency failed is never attempted. Absolute path of the JSONL mutation file inside the sandbox (e.g. /mnt/files/launch.jsonl). Write it with run_python or run_bash first. The first line declares the Opteo account id this file is written for: {"account": "tt_123"} (the Opteo accountId, in the Account Context section of your instructions). Required. Then one operation per line, each exactly {"kind": ..., "tempId": ..., "payload": {...}}. - kind is <entityType>.<operation>, e.g. "campaign.create". Entities: campaign, adgroup, ad, image, video, savedAudience, offlineEventSet, smartPlusCampaign, smartPlusAdgroup, smartPlusAd, gmvMaxCampaign, rfAdgroup, smartCreative, ruleAudience, lookalikeAudience, customerFileAudience, sparkPost. Call tiktokads_describe_mutation with a kind for its full field reference before writing lines for it. - tempId is a negative integer you choose to name this not-yet-created entity, unique within the file. - payload uses the field names tiktokads_describe_mutation documents for that kind. A later line references an earlier tempId by writing "optRefId:" followed by that same negative number in place of a real id, e.g. "campaign_id": "optRefId:-1". The declared account must be the account this tool is applying to — a mismatch refuses the whole file. This executes directly on MCP — no approval step, always explain what you're about to do first. Array results are returned as a JSONL dataset, also readable via resources/read.

tiktokads_call_readonly_rest_endpoint

ChatGPT
Read-only dispatcher for the TikTok Ads REST API. Use for fetching or searching entities such as campaigns, ad groups, ads, audiences, assets, etc. For reporting, use tiktokads_get_metrics. Array results are returned as a JSONL dataset, also readable via resources/read.

tiktokads_describe_mutation

ChatGPT
Returns the full payload field reference for one tiktokads_bulk_mutate operation kind — every field, whether it is required, its description, and the compatibility rules tiktokads_validate_bulk_mutate enforces on it (e.g. which optimization_goal values each campaign objective_type allows). Call before including that kind of operation in a tiktokads_bulk_mutate call.

tiktokads_describe_rest_endpoints

ChatGPT
Look up TikTok Ads REST API endpoints. Call this before tiktokads_call_readonly_rest_endpoint or tiktokads_get_metrics for any endpoint you have not already described this session. Available endpoints: report_integrated_get: Use this endpoint to create a synchronous report task. ad_aco_get: Use this endpoint to get creative materials for Smart Creative ads, including call-to-actions, texts, ad names, images, or video materials. This is a totally new endpoint in v1.3. ad_get: Use this endpoint to get the data of regular ads and Smart Creative ads. ad_review_info: Use this endpoint to get information about the review of ads. adgroup_get: Use this endpoint to obtain detailed information of an ad group or ad groups. adgroup_quota: Use this endpoint to get the dynamic quota on the number of active auction ad groups that an advertiser can have. adgroup_review_info: Use this endpoint to get information about the review of ad groups. adgroup_rf_estimated_info: Use this endpoint to query the estimated daily cost and frequency distribution. If the adgroup does not have a valid estimate, no corresponding record will be returned. advertiser_info: Use this endpoint to obtain the details of an advertiser's ad account. app_info: Use this endpoint to obtain the details of an App. app_list: Use this endpoint to obtain the list of apps under your account. audience_insight_overlap: Use this endpoint to get the details of the audience overlap between the benchmark audience and up to four other TikTok Custom Audience segments. blockedword_check: Use this endpoint to check if a list of words are blocked. blockedword_list: Use this endpoint to get the list of blocked words for an ad account. campaign_copy_task_check: Use this endpoint to check the results of an asynchronous copy task for a Manual Campaign. campaign_get: Use this endpoint to get all campaigns for an ad account. Optionally, you can use filters in your request to return only certain campaigns. campaign_gmv_max_info: Use this endpoint to retrieve the details of a GMV Max Campaign. campaign_gmv_max_session_get: Use this endpoint to retrieve the details of max delivery or creative boost sessions for a list of given session IDs. campaign_gmv_max_session_list: Use this endpoint to retrieve all active max delivery sessions for products or creative boost sessions for videos within a Product GMV Max Campaign. campaign_label_get: Use this endpoint to retrieve the list of campaign labels within an ad account. comment_task_check: Use this endpoint to check the status of a comment export task. creative_fatigue_get: Use this endpoint to detect whether Creative Fatigue occurred for creatives within a specified time range in the past. custom_conversion_get: Use this endpoint to retrieve the details of a Custom Conversion associated with a Pixel or an App. custom_conversion_list: Use this endpoint to retrieve the Custom Conversions associated with a Pixel or an App. discovery_detail: Use this endpoint to get detailed information about a popular hashtag, such as audience insights and hashtag status. discovery_trending_list: Use this endpoint to get a list of the top 200 popular hashtags on TikTok. discovery_video_list: Use this endpoint to retrieve the top 20 trending videos that use a popular hashtag in the selected top country of the hashtag and timeframe. These videos are ranked by a score that combines views, comments, likes, and shares. dmp_custom_audience_apply_log: Use this endpoint to get the latest application log of custom audiences. Note that this endpoint will only get the log of apply operation (action_mode = Apply). dmp_custom_audience_get: Use this endpoint to obtain the details of specified audiences, such as current status and modification record. Details for both owned and shared audiences will be returned. You can use is_creator to check whether this advertiser is the owner of this audience. dmp_custom_audience_list: Use this endpoint to get all audiences. The returned list is paginated with a maximum paging size of 100. For each audience, information like ID, name, type,…

tiktokads_get_metrics

ChatGPT
Run a TikTok Ads report and return the results as rows. Call tiktokads_describe_rest_endpoints with names: ["report_integrated_get"] first — valid dimensions/metrics combinations depend on report_type and data_level and are not enumerable here. Array results are returned as a JSONL dataset, also readable via resources/read.

tiktokads_lookup_enum

ChatGPT
Resolves TikTok Ads reference-data names or IDs. Call this before setting carrier_ids, device_model_ids, phone_region_code, or industry. Accepts a mix of names and IDs in one call and resolves both directions at once. For INDUSTRY, matches from both self-serve and non-self-serve Business Centers are returned together, each tagged with which one it applies to - the same industry_id can mean a different industry depending on the Business Center type. Each returned row is tagged with the query that found it; a query with no match anywhere has no row at all. Array results are returned as a JSONL dataset, also readable via resources/read.

tiktokads_validate_bulk_mutate

ChatGPT
Validate a file of TikTok Ads mutations without changing anything. Always run this on the file before calling tiktokads_bulk_mutate. Checks the file format, every field against tiktokads_describe_mutation's schemas, optRefId: referential integrity (a tempId must be declared before it is referenced, and only a create resolves to a real id), and — once those pass — TikTok's cross-field compatibility rules, including the ones that depend on a parent (e.g. an ad group's optimization_goal under its campaign's objective, or the optimization_goal and bid_type every ad group in a CBO campaign must share), read from the parent's create operation in this file or from its live state on TikTok. Those rules are the ones tiktokads_describe_mutation lists under each field, not all of TikTok's: a file that passes can still be rejected on apply (e.g. for a missing pixel or app), so when tiktokads_bulk_mutate reports such an error, fix it and validate again. It reads every existing campaign, ad group and ad the file names, so an id that isn't in this account fails here. tiktokads_bulk_mutate runs the same file checks itself before applying, so nothing slips through skipping this tool — it's a preview, not a gate. A file with problems fails with every problem at a stage named by line and the fix, all at once so a large file can be corrected in one pass — fix the file and re-validate as many times as you need, at no risk. A clean file returns changeSummary: every operation named against the live account, with its parents, current → new values and notes such as a disabled parent, what goes live (creates default to ENABLE), an ad update that clears fields it leaves out, or a tenfold budget increase. changeDetailsFilePath is a JSON file beside the mutation file with the full per-operation detail; read it only when you need more than the summary shows. Check changeSummary for anything you did not intend, then tell the user what it will change — especially what goes live and what is deleted — and apply only once they agree. MCP has no approval-card step, so this is the review. Absolute path of the JSONL mutation file inside the sandbox (e.g. /mnt/files/launch.jsonl). Write it with run_python or run_bash first. The first line declares the Opteo account id this file is written for: {"account": "tt_123"} (the Opteo accountId, in the Account Context section of your instructions). Required. Then one operation per line, each exactly {"kind": ..., "tempId": ..., "payload": {...}}. - kind is <entityType>.<operation>, e.g. "campaign.create". Entities: campaign, adgroup, ad, image, video, savedAudience, offlineEventSet, smartPlusCampaign, smartPlusAdgroup, smartPlusAd, gmvMaxCampaign, rfAdgroup, smartCreative, ruleAudience, lookalikeAudience, customerFileAudience, sparkPost. Call tiktokads_describe_mutation with a kind for its full field reference before writing lines for it. - tempId is a negative integer you choose to name this not-yet-created entity, unique within the file. - payload uses the field names tiktokads_describe_mutation documents for that kind. A later line references an earlier tempId by writing "optRefId:" followed by that same negative number in place of a real id, e.g. "campaign_id": "optRefId:-1". The declared account must be the account this tool is applying to — a mismatch refuses the whole file.

upload_assets

ChatGPT
Open an in-chat widget so the user can upload one or more images or videos from their device (phone/computer). Call this whenever the user wants to provide, add, or upload their own image(s) or video(s) and has not already given you a public URL — MCP clients have no chat attachments, so this widget is the only way for them to provide a local file. Do NOT ask them to paste a URL or attach a file. On completion, read the uploaded files back by calling the fetch_widget_output tool with the sessionId this tool returns — each file comes back as a public URL you then use for whatever the task needs.

view_images

ChatGPT
Fetch images from URLs and load them into your vision context so you can actually see them. Use this when the user needs you to look at visuals — analysing ad creatives, comparing screenshots, judging design, OCR'ing text from an image. URLs must point at publicly accessible images (jpeg, png, webp, or gif). Signed CDN URLs (e.g. fbcdn) work as long as the signature has not expired. Maximum 50 MB per image; large images are downscaled before you see them.

Capabilities

WritesInteractive

Example Prompts

Click any prompt to copy it.

Similar Apps