Technical reference
Marketools from your chat
Connect Marketools as an MCP server and work your brand where you already are: which ads competitors have active, which structures they repeat, and your on-brand version.
63 tools available
How to connect it
Five steps. No API key needed — the connection is authorized with your account.
- 01
Have a Marketools account
Use an existing brand or start onboarding from the chat by pasting your store URL. Marketools presents the Brand DNA it found for your confirmation.
- 02
Open your client's connector settings
In Claude that's Settings → Connectors → Add custom connector. Any MCP-compatible client works the same way.
- 03
Paste the server address
It's the only thing you have to copy.
https://app.marketools.ai/api/mcp - 04
Authorize with your account
We'll show you exactly which permissions you're granting before you accept. The basic permission can read and also create or modify your brand data; spending credits is a separate box you tick yourself.
- 05
Ask for what you need, in your own words
"Who am I competing with and which structures do they repeat?" or "make me my version of the ad that's been running longest". The client picks the tools for you.
About permissions
The basic permission is not read-only: on top of reading, it can create and modify your brand data (brands, products, ideal customers, angles, competitors and notes). The ones that spend — generating ads, finding competitors, monitoring sites, pulling comments or reviews — ask for a separate permission you grant explicitly. They're marked below. If your connection predates that permission, remove the connector and add it again.
Tools
This list is generated from the server, so it can't fall out of date. Required parameters are in bold; ones ending in "?" are optional.
Onboarding
Go from a website to a human-confirmed Brand DNA and a product or service ready to use — without leaving the conversation.
start_brand_onboardingwritesspendsreaches the internetCrawl a brand website and prepare its Brand DNA plus detected products/services. This uses paid crawler/AI capacity. After it returns, present the DNA proposal to the human and ask for explicit approval; never confirm it silently.
url string
get_brand_setup_statusread-onlyidempotentRead onboarding progress, the proposed DNA, detected products/services, and background enrichment status. Omit sessionId to read the latest session.
sessionId uuid?
confirm_brand_dnawritesidempotentPersist the crawler's proposed Brand DNA after the human has seen and explicitly approved it. Never call this on the user's behalf without asking. For visual changes, send the human to /brand/dna after confirmation.
sessionId uuid
select_brand_productwritesspendsidempotentChoose one detected product or service after DNA confirmation, plus the preferred product image/view when imageOptions contains more than one. This creates the product row and triggers paid AI enrichment in the background (Brand DNA, Brand Voice, and Product Brief generation, plus product image ingestion), so it requires the spend permission. Both selections must come from the human; never auto-pick.
sessionId uuid · productId string · preferredImageUrl url?
Brands
Where to start: which brands you can see, and all of a brand's context in one call.
list_brandsread-onlyidempotentList the brands the authenticated user can access. If none exist, the response explains how to start onboarding from this conversation.
get_brandread-onlyidempotentGet a brand by id, including its core fields. Use list_brands to find ids.
brandId uuid
get_contextread-onlyidempotentAssemble the full KB context for a brand (+ optional product/icp/angle): the bundle used to generate ads. Start here when you need everything at once. response_format controls the reply's text summary only (default 'concise': identity + DNA/Voice/confirmation status; 'detailed' adds the one-liner and value proposition). structuredContent always carries the full nested KB payload either way.
brandId uuid · productId uuid? · icpId uuid? · angleId uuid? · response_format concise | detailed?
create_brandwritesCreate a brand owned by the authenticated user. The brand is the root of the KB — create it before products, ICPs, or angles. `slug` is lowercase-kebab-case (e.g. "acme-co").
slug string · displayName string · websiteUrl value? · countryCode value? · languageCode value? · currencyCode value?
update_brandwritesidempotentUpdate a brand's core fields (name, slug, website, country, language, currency). Only the fields you pass are changed.
brandId uuid · slug string? · displayName string? · websiteUrl value? · countryCode value? · languageCode value? · currencyCode value?
Identity
Brand DNA and voice — what decides how every ad looks and sounds.
get_brand_dnaread-onlyidempotentGet a brand's DNA (positioning, design tokens, differentiators).
brandId uuid
get_brand_voiceread-onlyidempotentGet a brand's voice (tone attributes, vocabulary, example rewrites).
brandId uuid
upsert_brand_dnawritesidempotentCreate or replace a brand's DNA: positioning (one-liner, mission, vision, origin story), differentiators, category, target markets, design tokens (colors/fonts), and logo URLs. Pass the full DNA — this replaces the existing one.
brandId uuid · oneLiner value? · mission value? · vision value? · originStory value? · differentiators value? · designTokens value? · logoDarkUrl value? · logoLightUrl value? · logoIconUrl value? · category value? · targetMarkets value?
upsert_brand_voicewritesidempotentCreate or replace a brand's voice: tone attributes, sentence/rhythm rules, vocabulary (words + phrases to use/avoid), voice anchors, and example rewrites. Replaces the existing voice.
brandId uuid · toneAttributes value? · sentenceStructureRules value? · rhythmPatterns value? · voiceAnchors value? · wordsToUse value? · wordsToAvoid value? · phrasesCanonical value? · phrasesForbidden value? · exampleRewrites value?
Catalog
Products, briefs and photos. This is where an ad's content comes from.
list_productsread-onlyidempotentList products under a brand.
brandId uuid
get_productread-onlyidempotentGet a product by id.
productId uuid
get_product_briefread-onlyidempotentGet a product's Brief v2 (value prop, benefits, proof, offers, competitors).
productId uuid
list_product_assetsread-onlyidempotentList a product's image assets (packshots, cutouts, lifestyle, etc.) with their image URLs, in the deterministic order ad generation reads them — the first usable asset is the product's primary view. To change which view leads, show these options to the human and then call set_primary_product_asset with their choice.
productId uuid
create_productwritesCreate a product under a brand. Use list_brands / get_brand to find the brandId. `slug` is lowercase-kebab-case.
brandId uuid · slug string · displayName string · productUrl value?
update_productwritesidempotentUpdate a product's name, slug, or URL. Only the fields you pass are changed.
productId uuid · slug string? · displayName string? · productUrl value?
upsert_product_briefwritesidempotentCreate or replace a product's Brief (v2): value proposition, selling name, main benefits, proof points, main competitors, bad solutions, approved + forbidden claims, consumer blockers, and active offers. Replaces the existing brief. Never invent offers, competitor prices, or testimonials — only pass what you can verify.
productId uuid · valueProposition value? · sellingName value? · mainBenefits value? · proofPoints value? · mainCompetitors value? · badSolutions value? · approvedClaims value? · forbiddenClaims value? · consumerBlockers value? · activeOffers value?
add_product_assetwritesAdd an image asset to a product (packshot, cutout, on_model, before_after, ugc, or lifestyle). `url` must be a reachable image URL.
productId uuid · assetType packshot | cutout | on_model | before_after | ugc | lifestyle | concept | graphic · url string · aspectRatio value? · tags value? · position value?
set_primary_product_assetwritesidempotentMake one product photo the product's primary view — the image every subsequent ad generation for this product uses as its product reference. This is a durable, product-wide setting, not a per-conversation pin. Reach for it when the human says generation keeps using the wrong product view (the back label instead of the front, the bottle instead of the gummies). First call list_product_assets and show the human the options — it returns each view's image URL; the choice must come from the human; never auto-pick a view for them. Free: it only reorders the existing gallery (chosen asset first), no paid capacity. Returns the resulting asset order plus effectiveImageUrl — the photo generation will now actually use (a typed packshot can still outrank a lower-tier view; when that happens the response says so instead of pretending). `productId` comes from list_products; `assetId` from list_product_assets.
productId uuid · assetId uuid
Audience and angles
Who you're talking to, and from which angle.
list_icpsread-onlyidempotentList the ICPs (ideal customer profiles) for a product.
productId uuid
get_icpread-onlyidempotentGet an ICP by id, including its rich profile.
icpId uuid
list_anglesread-onlyidempotentList the messaging angles for an ICP.
icpId uuid
get_angleread-onlyidempotentGet a messaging angle by id.
angleId uuid
create_icpwritesCreate an ICP (ideal customer profile) under a product: demographics, geography, context story, values, frustrations, aspirations, vocabulary, and objection patterns.
productId uuid · name string · shortLabel value? · ageRanges value? · gender value? · location value? · incomeBracket value? · lifeStage value? · contextStory value? · coreValues value? · dailyFrustrations value? · aspirations value? · vocabularyCanonical value? · vocabularyAvoid value? · objectionPatterns value? · dataSources value?
update_icpwritesidempotentUpdate an ICP's fields. Only the fields you pass are changed.
icpId uuid · name string? · shortLabel value? · ageRanges value? · gender value? · location value? · incomeBracket value? · lifeStage value? · contextStory value? · coreValues value? · dailyFrustrations value? · aspirations value? · vocabularyCanonical value? · vocabularyAvoid value? · objectionPatterns value? · dataSources value?
create_anglewritesCreate a messaging angle under an ICP: the tension (context / frustration / desire), how the product solves it, and optional pain-point labels, desire statements, and proof hooks.
icpId uuid · label string · shortLabel value? · context string · frustration string · desire string · howProductSolves string · painPointLabels value? · desireStatements value? · proofHooks value?
update_anglewritesidempotentUpdate a messaging angle's fields. Only the fields you pass are changed.
angleId uuid · label string? · shortLabel value? · context string? · frustration string? · desire string? · howProductSolves string? · painPointLabels value? · desireStatements value? · proofHooks value?
Competitors
Who you're up against and what they're running right now in Meta's public ad library.
discover_competitorswritesspendsreaches the internetFind real competitors for a brand and start tracking them. It TRACKS pages; it does not collect their ads — the corpus fills on Marketools' Monday run, so a page tracked now can legitimately have zero ads for a while, and that is a wait rather than an empty market. It works from two sources, in this order: the competitor names already written on the brand's Product Brief (each resolved to its real Meta page), then a keyword sweep of the brand's own market first and the regional hubs after. A brief name that does not resolve to a page confidently is REPORTED, never guessed at: get_discovery_run lists it under unresolvedBriefNames so you can ask the human for that page instead of tracking the closest-sounding one. Runs in the background: this returns a runId immediately — poll get_discovery_run for the result, typically ~30s (p95 ~50s, measured 2026-07-30); the response's pollAfterSeconds and next tell you exactly when and how. Rate-limited per brand: 12 hours between runs once the brand tracks at least one competitor, but only 1 hour while it still tracks none — an empty first run must never lock a new account out for half a day. Calling it inside either window returns the previous run instead of starting a new one, and says what to do meanwhile. When it comes back with nothing, do not wait on it: add_brand_competitor tracks a known competitor immediately, for free. Requires editor access because it spends research budget.
brandId uuid
get_discovery_runread-onlyidempotentCheck a competitor-discovery run started by discover_competitors, and read the brand's competitor set. While the run is still 'running', the response carries expectedSeconds (estimated seconds REMAINING), pollAfterSeconds and next — respect them instead of polling in a tight loop. Once it is finished the response carries note and nextStep: a completed run with an empty competitors list is NOT evidence that the brand has no competitors, and note says which of the real reasons applies (the Product Brief hasn't been written yet, it names competitors that didn't resolve to a Meta page, or it names none). Read note and nextStep to the human instead of reporting an empty market. unresolvedBriefNames lists the competitor names on the brand's Product Brief that are NOT among the tracked pages — a non-empty list means the competitor set is INCOMPLETE even when competitors is long, so tell the human those names and ask for their Meta Page id rather than reporting the set as done. Newly discovered competitors arrive as 'candidate' — they are real pages with live ads, pending confirmation.
runId uuid
list_brand_competitorsread-onlyidempotentList the competitors currently tracked for a brand, with the market each is tracked in.
brandId uuid
add_brand_competitorwritesidempotentStart tracking a competitor by its Meta Page id, without running discover_competitors. Use this when discovery came back with zero candidates (its research budget is platform-wide and someone else can drain it first, and a 12h per-brand cooldown then blocks an immediate retry) or when the user already knows exactly who to track and just wants it added. This is a free knowledge-base write: it does NOT verify the page against Meta and does not spend any research budget or ad credit, so get the pageId from a tool that already reads real pages (search_competitor_ads / get_discovery_run on a brand that tracks it) or from the user directly — never invent one. Safe to call again for a page this brand already tracks, or previously removed with remove_brand_competitor — both are a no-op, not an error. Capped at 15 tracked competitors per brand (active + pending candidates combined); remove one with remove_brand_competitor to free a slot. pageId also accepts a Facebook URL that contains the id — facebook.com/p/Brand-Name-61558330702767/ (that URL shape is indexed by search engines, so you can find it even though the page itself blocks automated access), facebook.com/profile.php?id=…, or an Ad Library link with view_all_page_id. A vanity-handle URL like facebook.com/brandname does NOT contain the id and is refused with instructions — never guess one.
brandId uuid · pageId string · pageName string · country string? · thumbnailUrl url?
remove_brand_competitorwritesdeletesidempotentStop tracking a competitor: archives it out of this brand's active + candidate set, so it stops showing up in list_brand_competitors / search_competitor_ads / get_discovery_run and won't be re-suggested by a future discover_competitors run. Use this to prune a bad or irrelevant discovery candidate. Safe to call on a page that isn't tracked, or was already removed — it reports nothing changed rather than erroring. Reversible: add_brand_competitor on the same pageId later brings it back.
brandId uuid · pageId string
search_competitor_adsread-onlyidempotentSearch the ads this brand's tracked competitors are running. Read-only and free — it never fetches from the ad provider, so results come from what has already been collected. START WITH brandId ALONE: every filter is a narrowing, and `query` in particular is a LITERAL substring match against the ad's body text and page name — not a topic search — so ordinary category words ("tee", "t-shirt", "apparel") routinely return zero against a corpus full of exactly that product. When a result comes back empty the note says how many ads sit behind your filter; believe it over the filter. If the brand tracks nobody at all there is nothing here to find yet — add_brand_competitor tracks a known competitor instantly and for free, and discover_competitors finds them in the background. `daysRunning` is null when the data is too old to trust rather than guessing. Longevity and repetition are observable signals of sustained activity, not proof of conversions or ROAS. `templateStatus: 'ready'` means the ad's structure has been analysed and can be adapted immediately. response_format controls the reply's text summary only (default 'concise': an aggregate line + up to 5 ads); 'detailed' lists every returned ad. structuredContent always carries the full result either way.
brandId uuid · query string? · pageId string? · country string? · productCategory string? · activeOnly boolean? · minDaysRunning number? · limit number? · response_format concise | detailed?
get_competitor_adwritesreaches the internetidempotentGet one competitor ad in full. If its structure hasn't been analysed yet, this starts that analysis and returns templateStatus 'pending' — call again shortly and it usually becomes 'ready'. 'unavailable' is TERMINAL: the analysis can't succeed for that ad, so stop polling it. Adapting works either way; without a ready template it falls back to a category template instead of replicating that specific ad's structure.
brandId uuid · competitorAdId uuid
collect_competitor_adswritesspendsreaches the internetidempotentGo and fetch a tracked competitor's live ads from Meta right now, into this brand's corpus. Reach for it in exactly one situation: the brand tracks a competitor (list_brand_competitors shows it) but search_competitor_ads returns nothing for that page. That is normal and not a bug — adding a competitor records WHO to watch, it does not fetch their ads, and the automatic collection only runs weekly. This is the manual trigger, and it is the step that turns a tracked competitor into ads you can actually adapt. COSTS PROVIDER RESEARCH BUDGET (about one Ad Library page per call, billed to the account's monthly research budget — no ad credit, so get_quota is unaffected). Runs in the background and returns a jobId immediately; the response's expectedSeconds and pollAfterSeconds say when to check back, and the runtime estimate behind them is derived from the pipeline's shape, NOT from a measured percentile — this path is new and has no measured cohort yet, so treat the number as a rough plan, not a promise. To follow it, CALL THIS SAME TOOL AGAIN with the same brandId and pageId, and OMIT country — while a collection is running that re-call is free, reads the run in progress and never starts a second fetch, whichever market the run is under. A run is identified by brandId + pageId + country, so if you polled with a DIFFERENT country than the one you started, you would be asking for a second market — a separate, paid collection. Omitting country on the poll is what keeps it free. When it reports completed, call search_competitor_ads to read the ads. It will not spend when it doesn't have to: if the corpus is already fresh for that page it says so and charges nothing, and a page that came back with zero ads is not re-fetched for an hour. A completed run reporting 0 ads is a real answer about that page in that market — say so plainly instead of retrying in a loop; try another market with country, or check the pageId with the human.
brandId uuid · pageId string · country string?
Create
Take an active competitor reference and get your version. The structure is theirs; the product, message and brand are yours.
list_adsread-onlyidempotentList generated ads for a brand (optionally filtered by product or status).
brandId uuid · productId uuid? · status generating | completed | failed?
get_adread-onlyidempotentGet a generated ad by id (image URLs, copy, status). When our visual grader has evaluated the render, the payload includes qa: {state, defects} — state 'flagged' means a real defect survived (a leftover competitor offer, clipped text, a placeholder typeset into the image); 'completed' means it rendered, NOT that it is safe to publish, so tell the user about flagged defects instead of presenting the ad as finished. No qa field means the ad was never graded. The reference the ad was structurally modeled on is returned as the 'benchmark reference (structure source)' resource link. The image URLs are public, so an ad that is not flagged can be handed straight to the human's own Meta Ads connector to upload as a creative — Marketools never publishes to Meta itself.
adId uuid
count_ads_this_monthread-onlyidempotentCount ads generated this calendar month against the authenticated user's quota (their owned brands' ads, including those generated by collaborators).
get_quotaread-onlyidempotentCheck what is left to spend. WITHOUT brandId it answers for the CONNECTED ACCOUNT — plan, this month's ad credits (used / remaining) and the monthly research budget in dollars, with what one competitor-ad deconstruct costs — so it works before any brand exists, which is exactly when the first spend (start_brand_onboarding) happens. WITH brandId it answers for that brand's OWNER instead: plan, monthly limit, ads used this month and how many remain — the exact numbers adapt_competitor_ad (and any other ad generation) checks before it refuses with 'quota'. That distinction matters on a shared brand: the brand-scoped answer is the owner's cap, the account-level one is the caller's own. Calling this before generating avoids spending a user's attention framing a request the cap is certain to refuse. Free and read-only. Note: the research budget is reported only in the account-level answer, because it is metered per acting user rather than per brand.
brandId uuid?
adapt_competitor_adwritesspendsreaches the internetidempotentTurn a competitor's ad into this brand's own ad: it borrows the competitor's STRUCTURE (layout, framing, copy formula) and fills it with the brand's own products, message and identity — never the competitor's message. Costs one ad credit from the brand's monthly quota. Returns a jobId immediately; poll get_ad_job for the image. The full pipeline (deconstruct + render) is typically ~321s (p95 ~362s, measured 2026-07-30) — this is normal, not a hang; the response's expectedSeconds, pollAfterSeconds and next tell you exactly when and how to poll. By DEFAULT it renders BOTH the feed and the vertical story — still ONE ad credit (a job spends one credit however many formats it renders), but the story is a second render that runs after the feed, so expect roughly ~631s end to end (derived from the measured 2026-07-30 constants: the single-format pipeline plus one more render; not yet measured as its own pipeline). Pass formats: ["feed"] for the feed alone when the human only wants the square one and wants it sooner. The feed's aspect ratio is NOT fixed at 4:5: it follows the competitor ad's own shape (1:1, 4:5…), because the benchmark supplies the form. Say which shape came back rather than assuming. Pass a stable idempotencyKey if you may retry — retrying with the same key returns the same job instead of generating (and charging for) a second ad. Formats do NOT fork that key: the same key with different formats returns the original job unchanged (the response echoes which formats it actually renders) — use a fresh key if you want a new render with other formats. If a job already completed with only one format and the human now wants the other (e.g. "I also want the vertical version"), calling this again with a fresh key is a NEW job — a second ad credit, not a free top-up of the first — say that cost out loud before you place it.
brandId uuid · competitorAdId uuid · productId uuid? · icpId uuid? · formats array of feed | story? · idempotencyKey string?
edit_adwritesspendsreaches the internetidempotentFix or change an ad that has ALREADY rendered, by describing the change in plain language — 'the logo is huge, shrink it', 'change the headline to X', 'make the background darker'. It EDITS the existing image in place (the model receives the generated ad and applies only the requested change) — it never re-renders from the competitor reference, so everything the human liked survives. Reach for it when the human asks for a tweak on a generated ad, and especially when get_ad_job or get_ad reports qa 'flagged': this is the tool that fixes the defect. adId is the ad to fix (the adId a completed get_ad_job returned, or one from list_ads); any version of the family works — the edit always applies ON TOP of the family's latest completed version, so consecutive edits accumulate, and it covers every format the ad has (feed, plus story when one exists) in the same pass. Cost, up front: the first 3 edits of an ad are free; after that an edit costs 1 ad credit, and a call without confirmPaid refuses (charging nothing) so you can ask the human first — only after they explicitly accept the charge, call again with confirmPaid: true; never set it on your own. Every successful response includes freeEditsRemaining, so you know whether the NEXT edit will cost BEFORE proposing it. Returns a jobId immediately; poll get_ad_job for the result — an edit typically runs ~310s (p95 ~644s; derived from the measured 2026-07-30 render constants — edits are not yet measured as their own pipeline). Pass a stable idempotencyKey if you may retry — the same key returns the same job instead of dispatching (and possibly charging) a second edit.
brandId uuid · adId uuid · instruction string · confirmPaid boolean? · idempotencyKey string?
get_ad_jobread-onlyidempotentCheck an ad job started by adapt_competitor_ad or edit_ad (the response's kind field says which). For adapt jobs, status goes 'deconstructing' (analysing the competitor's structure) → 'generating' (rendering) → 'completed' with the image URLs, or 'failed' with a reason; the whole pipeline is typically ~321s (p95 ~362s, measured 2026-07-30). Edit jobs skip the analysis and go straight to rendering the changed image, so they finish faster (edit_ad quotes their ETA). While a job is running, the response carries expectedSeconds (estimated seconds REMAINING), pollAfterSeconds and next — respect them instead of polling in a tight loop. The response's formats field says which formats the job renders; adapt jobs that requested both formats take roughly one render longer, and their ETA fields already account for it. A null storyImageUrl on a completed job whose formats is ["feed"] means the story was never requested — not a failure. IMPORTANT: a completed ad may still carry `qa.state: 'flagged'` with defect kinds — our own visual grader found a real problem in the render (a leftover offer, clipped text, a placeholder typeset into the image). 'completed' means it rendered, NOT that it is safe to publish. When qa is flagged, tell the user what the defect was and offer to fix it with edit_ad (a plain-language edit of the rendered image) rather than presenting the ad as finished.
jobId uuid
mark_ad_runnablewritesidempotentRecord what the human actually decided about a finished ad: are they running it on Meta as-is, with no designer and no touch-up? Call it as soon as they say so — after get_ad_job completes, after an edit, or whenever they tell you an ad was good or useless. Free: it never costs a credit or research budget. runnable: true means it ships as-is; runnable: false needs a reason from a fixed list (product_wrong, copy_wrong, claim_not_defensible, off_brand, crop_or_clipping, format_wrong, other) and optionally note with the human's own words. Never guess the answer or infer it from their tone — only record what they actually said, because this number decides what we fix next. One answer per ad: the first one is kept and a second call changes nothing (an edited ad is a new adId and gets its own answer). Returns what was recorded; on a 'no' it also names the free way to try to rescue that ad with edit_ad.
adId uuid · runnable boolean · reason product_wrong | copy_wrong | claim_not_defensible | off_brand | crop_or_clipping | format_wrong | other? · note string?
Voice of customer
Reviews and comments: where the real objections and your customer's own words come from.
get_ad_commentswritesspendsreaches the internetRead the comments on a public Facebook post or ad. This is where real objections and real language show up — often better copy input than the ad itself. Nothing is stored; the comments come back for you to reason with. Costs provider credits. If the post is private or deleted you'll get a clear 'not found' rather than an empty list.
brandId uuid · postUrl url · limit number?
harvest_reviewswritesspendsreaches the internetRead customer reviews off the brand's own public pages and store them. Reviews are the best source of real objections and real customer language for ad copy. Scope note worth passing on to the user: this reads structured review markup (the same data Google reads), which covers Shopify stores and review apps like Judge.me, Loox and Yotpo. It does NOT read Trustpilot or Amazon — those block automated readers, so a zero result means 'not published in a readable form here', not 'this brand has no reviews'.
brandId uuid · url url?
add_customer_reviewwritesidempotentStore ONE real customer review that the human gave you in the conversation — pasted, forwarded or read out. Free, instant, no crawling. Reach for it when a structure needs a testimonial and the brand has no reviews on file (adapt_competitor_ad refuses with exactly that message), or when harvest_reviews came back empty because the reviews live somewhere it cannot read: Amazon, Trustpilot, a Google Business profile, a review app without public markup, or a customer's email. It lands in the same place harvest_reviews writes, so a later ad can quote it verbatim and the brand is never asked for it again. The words must be the CUSTOMER's, exactly as the human gave them — never write, paraphrase, summarize or reconstruct a review yourself, and never pass an ICP, a persona or an example as one; what you send is stored as this brand's own customer's words with the connected account stamped on it. Where it came from is REQUIRED: pass sourceUrl or sourceNote, because a customer quote with no recorded origin cannot be defended if the ad is ever questioned. Returns the stored reviewId, whether it was already on file, and whether an ad may quote it (a 1-3 star review is stored and useful for get_review_themes, but is never quoted under a five-star layout). Then re-run adapt_competitor_ad on the same competitorAdId.
brandId uuid · body string · author string? · rating number? · sourceUrl url? · sourceNote string?
search_reviewsread-onlyidempotentSearch the customer reviews already collected for this brand. Read-only and free. Filter by rating to read the complaints (low) or the praise (high) — the low ones are usually where the useful copy is, because they name the objection you have to answer.
brandId uuid · query string? · minRating number? · maxRating number? · limit number?
get_review_themesread-onlyreaches the internetSummarize this brand's stored reviews into the recurring themes and objections behind them — the raw material for ad angles. Returns an empty list when there are no reviews stored yet; it will not invent themes, so an empty result means run harvest_reviews first.
brandId uuid · limit number?
Market
What's being said in short-form video, and what changed on the sites you watch.
monitor_brand_sitewritesspendsreaches the internetTake a snapshot of a website and report what changed since the last one — prices, products added or pulled, promos starting or ending, positioning copy. Works on the brand's own site (default) and on a competitor's if you pass their url. The first run has nothing to compare against and just records the baseline; call it again later to see movement.
brandId uuid · url url? · kind own | competitor?
get_site_changesread-onlyidempotentRead the history of website snapshots for this brand and what changed between consecutive ones. Read-only and free — it does not fetch anything, it reads what monitor_brand_site already captured.
brandId uuid · url url?
search_tiktok_videoswritesspendsreaches the internetSearch TikTok for short-form video on a topic and save what it finds — useful for reading how a category is being talked about right now, and which hooks are getting engagement. Costs provider credits, so it's rate-limited. Use search_organic_social afterwards to re-read the results for free.
brandId uuid · query string · limit number?
search_organic_socialread-onlyidempotentRead the organic social posts already collected for this brand. Read-only and free — never calls the provider, so it can be looped over safely.
brandId uuid · query string? · platform tiktok | instagram? · limit number?
Memory
What's worth remembering between conversations: what you already tried, what you can't say, what worked.
rememberwritesidempotentSave something worth remembering about this brand for future conversations — a constraint ('legal won't approve the word cure'), a result ('the before/after angle underperformed in June'), a preference. This is for what working with the brand has TAUGHT us; facts about what the brand IS belong in the knowledge base (upsert_brand_dna, upsert_product_brief). scope 'brand' (default) is shared with everyone on the brand; 'user' is private to you.
brandId uuid · key string · value string · scope brand | user?
recallread-onlyidempotentRead back what's been remembered about this brand — everything shared on the brand, plus your own private notes, never someone else's. Worth calling before proposing creative, so you don't re-suggest something that already failed.
brandId uuid · query string? · limit number?
forgetwritesdeletesidempotentDelete a remembered item by key. Only removes a shared brand note or YOUR OWN private one — another person's private note is not yours to delete, even on a brand you can edit.
brandId uuid · key string · scope brand | user?
Weekly agent
The competitor benchmark that runs every Monday: read it any time, and turn the send on or off.
get_weekly_briefread-onlyidempotentRead this brand's weekly competitor benchmark. Every Monday Marketools compares the brand against its tracked competitors and proposes ads from what is winning — reach for this whenever the human asks 'what's the competition doing', 'show me this week's brief', or similar. Returns the latest brief by default, or a specific one with `briefId`: which competitors were benchmarked (with strength and who's new/winning), the week's finding, proposed ads with links and thumbnails to any that rendered, and what changed in the brand's own store since last week. Free and read-only. If this brand has never had a brief, the response says exactly why (no briefed product yet, the digest is off, or it just hasn't run) and what to do right now — never a bare 'not found'.
brandId uuid · briefId uuid?
configure_weekly_agentwritesidempotentConfigure the weekly competitor benchmark for a brand — the same Monday digest get_weekly_brief reads from. Pass `enabled: true` or `enabled: false` to turn it on or off; omit `enabled` to just read the current setting without changing anything. IMPORTANT: this toggle is per BRAND OWNER, not per brand — turning it off pauses the Monday email for every brand that owner has, and the response says how many other brands that is. Free. Requires editor access on the brand (a viewer can read the brief with get_weekly_brief but not reconfigure it). Cadence is fixed at every Monday and is not configurable; which competitors get benchmarked is managed with add_brand_competitor / remove_brand_competitor / list_brand_competitors, not here.
brandId uuid · enabled boolean?
Help & feedback
Where to start, and how to tell us what's missing: a guide matched to your account's real state, feedback straight to the founders, and a register of the capabilities you wish existed.
getting_startedread-onlyidempotentStart here. Returns a short guide matched to this account's REAL state, read fresh every call: no brand yet → the onboarding steps; a brand whose DNA still awaits human confirmation → the confirmation step; a confirmed brand that tracks no competitors, or whose competitors have no ads collected yet → the one concrete step that unblocks it (and the free escape hatch when discover_competitors is on cooldown); a brand that can actually reach an ad → the generation workflows with example prompts to offer the human. It says 'ready' only after checking that the brand tracks competitors AND that their ads are already collected — it does not pre-check the ad-credit quota or your role, so a 'ready' brand can still be refused for those (get_quota tells you before you spend). Also summarizes which tools spend money and how the spend permission is granted. Free and read-only — call it at the start of a conversation or whenever you are unsure what to do next.
send_feedbackwritesDeliver the human's feedback about Marketools straight to the founders — bug reports, confusing results, missing capabilities, praise. Use it when the human says something that is feedback about the product itself; confirm with them before sending on their behalf. Free: it records the message and emails the team, nothing else changes on the account. Returns a confirmation; tell the human it was received, and never promise a response time. `context` is optional — the tool name or jobId the feedback concerns.
message string · context string?
get_more_toolsread-onlyidempotentCall this when the human asks for something no current tool covers (e.g. video ads, bulk exports). It records the request so the team sees real demand, and answers with the closest tools that DO exist today — it does not unlock anything. Free. Never promise the user a timeline or that the capability will be built. It also answers the boundary question: anything about spend, ROAS, CPA, budgets, audiences, publishing or pausing on Meta is the human's own Meta Ads connector's job, not Marketools', and this tool says so and explains how to hand a finished ad over to it.
capability string
If something isn't working
- The connector was added but no tools show up.
- It's almost always the address: it has to end in /api/mcp. The domain on its own doesn't answer as an MCP server.
- A tool says it's missing a permission.
- Your connection was authorized before that permission existed. It won't ask you again on its own: remove the connector and add it back.
- I asked for an ad and nothing arrived.
- Generating typically takes around five minutes (measured on the real pipeline) and doesn't block the reply: the tool returns an identifier and the result is checked separately. If it still doesn't show up after that, ask for the status of that identifier.