Public leaderboard

Public assessment

AppVisionOS/apple-search-ads-mcp (apple-search-ads-mcp)

apple-search-ads-mcp · v1.0.0 · scanned

What changed in the harness

Selection accuracy 98→100, token cost up 8%, unconfirmed writes 0%→0%.

Category breakdown

Where the score comes from.

Earned points across the four signals Gradable measures. Safety and Legibility are scored out of 30; Economics and Discoverability are scored out of 20.

01Safety

0.0 / 30

0.0 out of 30
02Legibility

18.5 / 30

18.5 out of 30
03Economics

7.3 / 20

7.3 out of 20
04Discoverability

11.7 / 20

11.7 out of 20

Highest-impact fix

Estimated gain +30 points

Add explicit identity and permission preflight tools

Expose machine-readable principal/tenant confirmation and a non-mutating permission check so agents can verify both before destructive actions.

Description evidence

Defects and rewrites.

67 defects found across the exposed tool descriptions. Suggested rewrites make purpose, inputs, boundaries, and returns easier for an agent to understand.

Tool Defect types Suggested rewrite
search_geo
params_unexplained no_return_description
Search geo entities (Country / AdminArea / Locality) by `query`, the name (or partial name) of the geo you are looking for when building geo targeting. Returns the matching geo entities; pass `countrycode` (lowercase, on purpose, as Apple is case-sensitive here) to scope an AdminArea or Locality search to one country, and use `limit`/`offset` to paginate.
apps_get
params_unexplained
Fetch App Store metadata for the app with the given numeric `adamId` (the App Store app id, found via `search_apps`). Returns appName, developerName, primaryGenre, secondaryGenre, iconPictureUrl, availableStorefronts, deviceClasses, and other metadata.
apps_locale_details
params_unexplained
Get the localized default product-page details for the app with the given numeric `adamId` (the App Store app id, found via `search_apps`). Returns, per locale, the subtitle, short description, screenshots, and app preview used on the default product page.
apps_eligibilities_find
params_unexplained
Find app-eligibility records for the app with the given numeric `adamId` (the App Store app id, found via `search_apps`), using `selector` conditions (filterable by supplySource, countryOrRegion, deviceClass, state) plus orderBy/pagination. Returns the matching eligibility records.
apps_assets_find
params_unexplained
Find App Store screenshot / app-preview assets for the app with the given numeric `adamId` (the App Store app id, found via `search_apps`), using the `selector` to filter. Returns assetGenIds you can use to author creatives or audit rejected assets.
cpp_list
params_unexplained
List the Custom Product Pages configured for the app with the given numeric `adamId` (the App Store app id, found via `search_apps`). Returns the app's Custom Product Pages with their ids, usable with `cpp_get` and `cpp_locale_details`.
cpp_get
params_unexplained
Fetch a single Custom Product Page for the app with the given numeric `adamId` (the App Store app id), identified by its `productPageId` (obtained from `cpp_list`). Returns that Custom Product Page's full details.
cpp_locale_details
params_unexplained
List the locales available for a Custom Product Page of the app with the given numeric `adamId`, identified by its `productPageId` (obtained from `cpp_list`). Returns the available locales, each with its localized assets.
campaigns_create
params_unexplained no_return_description
Create a new campaign and return the created campaign including its id. The minimum useful body specifies adamId, name, countriesOrRegions, adChannelType, supplySources, billingEvent, and a budget. Set `paymentModel` (LOC, PAYG, or NOTCARD) to match your account's billing type; LOC accounts additionally use `locInvoiceDetails` and `budgetOrders`.
campaigns_get
params_unexplained
Fetch one campaign identified by its numeric `campaignId` (obtained from `campaigns_list` or `campaigns_find`). Returns the campaign's full details, including name, status, budgets, schedule, and targeting settings.
campaigns_find
no_return_description
Search for campaigns matching the `selector` conditions (operators such as EQUALS, IN, CONTAINS; supports fields, orderBy, and pagination). Returns the matching campaigns with the requested fields, e.g. to locate a campaignId for `campaigns_get` or `campaigns_update`.
campaigns_update
params_unexplained no_return_description
Update the campaign identified by the numeric `campaignId` (obtained from `campaigns_list`), passing a partial `campaign` object with only the fields you want to change. Set clearGeoTargetingOnCountryOrRegionChange=true if you change countriesOrRegions so ad-group geo targeting is cleared. Returns the updated campaign.
campaigns_delete
name_restates_behavior params_unexplained no_return_description
Remove the campaign identified by the numeric `campaignId` (obtained from `campaigns_list`) from your org. Deleting is permanent and cannot be undone, so confirm the id first. Returns a success response once the campaign is deleted.
adgroups_create
params_unexplained no_return_description
Create a new ad group within the campaign identified by `campaignId` (obtained from `campaigns_list`/`campaigns_find`) and return the created ad group. `name` is required (max 200 chars); `endTime` is an optional ISO 8601 end datetime; `status` may be ENABLED or PAUSED (default ENABLED); use `extra` to pass additional targeting fields verbatim to Apple. Bid and targeting live on the ad group, not the campaign.
adgroups_get
params_unexplained
Fetch one ad group identified by its numeric `adGroupId`, scoped to the campaign with the given `campaignId` (both obtainable from `campaigns_list` and `adgroups_list`). Returns the ad group's details, including its bids, status, and targeting dimensions.
adgroups_list
params_unexplained
List all ad groups belonging to the campaign with the given `campaignId` (obtained from `campaigns_list` or `campaigns_find`), paginated via `limit`/`offset`. Returns the campaign's ad groups.
adgroups_find_in_campaign
params_unexplained no_return_description
Find ad groups within the campaign identified by `campaignId` (obtained from `campaigns_list` or `campaigns_find`) that match the `selector` conditions (operators like EQUALS/IN/CONTAINS, with orderBy and pagination). Returns the matching ad groups with the requested fields.
adgroups_find_org_wide
no_return_description
Find ad groups across the entire org that match the `selector` conditions (operators like EQUALS/IN/CONTAINS, with orderBy and pagination). Useful when you do not know the parent campaignId. Returns the matching ad groups with the requested fields.
adgroups_update
params_unexplained no_return_description
Update an existing ad group identified by campaignId (the campaign it belongs to) and adGroupId. The adGroup object carries the fields to change — set only the ones you want updated: name, status (ENABLED/PAUSED), startTime/endTime, defaultBidAmount, cpaGoal, biddingStrategy (MANUAL_CPT or MAX_CONVERSIONS), automatedKeywordsOptIn, or targetingDimensions (all dimensions use { included: [...], excluded: [...] }, extra fields are forwarded to Apple). Returns the updated ad group.
adgroups_delete
params_unexplained no_return_description
Permanently remove the ad group identified by campaignId (the campaign it belongs to) and adGroupId. Returns the deleted ad group.
creatives_create
params_unexplained no_return_description
Create a creative that references a default product page, custom product page, or creative set; ads bind to creatives via creativeId. Provide name (a label for the creative), adamId (the app's Adam ID), type (CUSTOM_PRODUCT_PAGE, DEFAULT_PRODUCT_PAGE, or CREATIVE_SET), and productPageId when type=CUSTOM_PRODUCT_PAGE; extra passes additional fields through to Apple verbatim. Returns the created creative including its creativeId for use with ads_create.
creatives_list
name_restates_behavior no_return_description
Return one page of creatives for the org at a time. Use limit (max 1000, Apple's hard cap) and offset to page through results, requesting with increasing offset until fewer than limit items are returned. Returns an array of creatives with their IDs, types, and product-page references.
creatives_get
params_unexplained no_return_description
Return the full details of a single creative identified by creativeId (obtain IDs via creatives_list or creatives_find).
creatives_find
name_restates_behavior no_return_description
Search for creatives org-wide using a selector: conditions filter on fields with operators like EQUALS, IN, or BETWEEN; fields selects which attributes to return; orderBy sorts results; pagination controls limit and offset. Returns the creatives matching the selector.
ads_create
params_unexplained no_return_description
Create an ad inside the ad group adGroupId of campaign campaignId. The ad object requires name and creativeId (returned by creatives_create) and optionally creativeType and status (ENABLED/PAUSED); ads bind a creative to the ad group rather than directly to a CPP or creative set. Returns the created ad including its adId.
ads_get
params_unexplained no_return_description
Return the full details of a single ad identified by adId within adGroupId of campaign campaignId.
ads_list
name_restates_behavior params_unexplained no_return_description
Return one page of ads belonging to the ad group adGroupId in campaign campaignId at a time. Use limit (max 1000) and offset to page through results, requesting with increasing offset until fewer than limit items are returned. Returns an array of ads with their IDs, statuses, and creative bindings.
ads_find_in_campaign
name_restates_behavior params_unexplained no_return_description
Search for ads across all ad groups in campaign campaignId using a selector: conditions filter on ad fields with operators like EQUALS, IN, or BETWEEN; fields selects returned attributes; orderBy sorts results; pagination controls limit and offset. Returns the ads matching the selector.
ads_find_org_wide
name_restates_behavior no_return_description
Search for ads across the entire org using a selector: conditions filter on ad fields with operators like EQUALS, IN, or BETWEEN; fields selects returned attributes; orderBy sorts results; pagination controls limit and offset. Returns the ads matching the selector.
ads_update
params_unexplained no_return_description
Update the ad identified by campaignId, adGroupId, and adId. The ad object carries the fields to change — typically status, name, or repointing to a different creativeId (create the new creative first via creatives_create). Returns the updated ad.
ads_delete
params_unexplained no_return_description
Permanently remove the ad identified by campaignId, adGroupId, and adId. Returns the deleted ad.
targeting_keywords_create
name_restates_behavior params_unexplained no_return_description
Bulk-add positive (targeting) keywords to the ad group adGroupId in campaign campaignId, up to 1000 keywords per call. Each keywords entry requires text (the phrase), matchType (BROAD matches close variants and synonyms; EXACT matches only the literal phrase), and bidAmount (per-keyword bid), with optional status. Returns the created keywords with their IDs.
targeting_keywords_get
params_unexplained no_return_description
Return the full details of a single targeting keyword identified by keywordId within adGroupId of campaign campaignId.
targeting_keywords_list
name_restates_behavior params_unexplained no_return_description
Return one page of targeting keywords on the ad group adGroupId in campaign campaignId at a time. Use limit (max 1000) and offset to page through results, requesting with increasing offset until fewer than limit items are returned. Returns an array of keywords with text, matchType, bid, and status.
targeting_keywords_find
params_unexplained no_return_description
Search for targeting keywords across all ad groups in campaign campaignId using a selector (conditions, fields, orderBy, pagination). Apple offers no per-ad-group find endpoint, so filter by adGroupId in the selector conditions when needed. Returns the keywords matching the selector.
targeting_keywords_update
name_restates_behavior params_unexplained no_return_description
Bulk-update existing targeting keywords on the ad group adGroupId in campaign campaignId, up to 1000 per call. Each keywords entry must include id (returned from a prior create/list call) plus the fields to change: text, matchType, bidAmount, or status. Returns the updated keywords.
targeting_keywords_delete
name_restates_behavior params_unexplained no_return_description
Bulk-remove targeting keywords from the ad group adGroupId in campaign campaignId by passing keywordIds (up to 1000 per call). Returns the deleted keywords.
targeting_keywords_delete_single
name_restates_behavior params_unexplained no_return_description
Remove a single targeting keyword identified by keywordId from the ad group adGroupId in campaign campaignId. Unlike the bulk variant, uses Apple's REST DELETE endpoint. Returns the deleted keyword.
adgroup_negative_keywords_create
name_restates_behavior params_unexplained no_return_description
Bulk-add negative keywords to the ad group adGroupId in campaign campaignId, up to 1000 per call, to prevent ads from showing for unwanted searches. Each keywords entry requires text and matchType (BROAD or EXACT) with optional status. Returns the created keywords with their IDs.
adgroup_negative_keywords_get
params_unexplained no_return_description
Return the full details of a single ad-group-level negative keyword identified by keywordId within adGroupId of campaign campaignId.
adgroup_negative_keywords_list
name_restates_behavior params_unexplained no_return_description
Return one page of ad-group-level negative keywords on the ad group adGroupId in campaign campaignId at a time. Use limit (max 1000) and offset to page through results, requesting with increasing offset until fewer than limit items are returned. Returns an array of keywords with text, matchType, and status.
adgroup_negative_keywords_find
name_restates_behavior params_unexplained no_return_description
Search for ad-group-level negative keywords across all ad groups in campaign campaignId using a selector (conditions, fields, orderBy, pagination). Returns the keywords matching the selector.
adgroup_negative_keywords_update
name_restates_behavior params_unexplained no_return_description
Bulk-update ad-group-level negative keywords on the ad group adGroupId in campaign campaignId, up to 1000 per call. Each keywords entry must include id plus the fields to change: text, matchType, or status. Returns the updated keywords.
adgroup_negative_keywords_delete
name_restates_behavior params_unexplained no_return_description
Bulk-delete up to 1000 ad-group-level negative keywords within a single ad group, scoped by campaignId and adGroupId; keywordIds lists the negative keyword IDs to remove. Returns the deleted negative keyword objects.
campaign_negative_keywords_create
name_restates_behavior params_unexplained no_return_description
Bulk-create up to 1000 campaign-level negative keywords for the given campaignId. Each entry requires text plus a BROAD or EXACT matchType and optionally an ACTIVE/PAUSED status; campaign-level terms block matching search impressions across all ad groups in the campaign. Returns the created negative keyword objects.
campaign_negative_keywords_get
name_restates_behavior params_unexplained no_return_description
Fetch a single campaign-level negative keyword by campaignId and keywordId to inspect its text, match type, and status. Returns the matching negative keyword object.
campaign_negative_keywords_list
name_restates_behavior params_unexplained no_return_description
List campaign-level negative keywords for the given campaignId, paginated with limit (max 1000 per page) and offset. Returns a page of negative keyword objects along with pagination metadata.
campaign_negative_keywords_find
name_restates_behavior params_unexplained no_return_description
Find campaign-level negative keywords within one campaign (campaignId) using a selector whose conditions (EQUALS, IN, CONTAINS, etc.) filter on fields, with fields/orderBy/pagination applied to the results. Returns the matching negative keyword records.
campaign_negative_keywords_update
name_restates_behavior params_unexplained no_return_description
Bulk-update up to 1000 campaign-level negative keywords for the given campaignId; each entry must set the existing keyword id and may change text, matchType (BROAD/EXACT), or status (ACTIVE/PAUSED). Returns the updated negative keyword objects.
campaign_negative_keywords_delete
name_restates_behavior params_unexplained no_return_description
Bulk-delete up to 1000 campaign-level negative keywords by their keywordIds for the given campaignId. Returns the deleted negative keyword objects.
reports_campaigns
params_unexplained
Campaign-level performance report. Returns rows with impressions, taps, installs, spend, CPT/CPM/CPI, conversion rate, etc. Pass groupBy to pivot by country, device, age, gender. Set returnRecordsWithNoMetrics to include entities with zero activity in the period, returnRowTotals to add per-row subtotals, and returnGrandTotals to append an overall grand-total row.
reports_adgroups
name_restates_behavior params_unexplained no_return_description
Ad-group-level performance report within a single campaign. Pass campaignId plus startTime/endTime and optionally groupBy to pivot rows; returns per-ad-group rows with metrics such as impressions, taps, and installs. Set returnRecordsWithNoMetrics to include inactive ad groups, returnRowTotals for subtotal rows, and returnGrandTotals for a grand-total row.
reports_keywords_in_campaign
name_restates_behavior params_unexplained no_return_description
Keyword report rolled up across all ad groups in one campaign, over the requested time range. Pass campaignId and startTime/endTime; returns per-keyword rows with performance metrics like impressions, taps, and installs. Set returnRecordsWithNoMetrics to include zero-activity keywords, returnRowTotals for subtotal rows, and returnGrandTotals for a grand-total row.
reports_keywords_in_adgroup
params_unexplained no_return_description
Keyword report scoped to a single ad group within a campaign (campaignId + adGroupId), over the requested time range. Returns per-keyword rows with performance metrics like impressions, taps, and installs. Set returnRecordsWithNoMetrics to include zero-activity keywords, returnRowTotals for subtotal rows, and returnGrandTotals for a grand-total row.
reports_search_terms_in_campaign
params_unexplained no_return_description
Search-terms report rolled up across all ad groups in a campaign, over the requested time range. Returns rows of the actual queries users typed with per-term metrics such as impressions and taps - use it to harvest new keywords or negatives. Set returnRecordsWithNoMetrics to include terms with no activity, returnRowTotals for subtotal rows, and returnGrandTotals for a grand-total row.
reports_search_terms_in_adgroup
name_restates_behavior params_unexplained no_return_description
Search-terms report scoped to a single ad group within a campaign (campaignId + adGroupId), over the requested time range. Returns rows of the actual queries users typed with per-term metrics such as impressions and taps. Set returnRecordsWithNoMetrics to include terms with no activity, returnRowTotals for subtotal rows, and returnGrandTotals for a grand-total row.
reports_ads_in_campaign
params_unexplained no_return_description
Ad-level performance report, one row per Custom Product Page or creative variation, rolled up across the campaign over the requested time range. Returns rows with ad-level metrics such as impressions, taps, and spend. Set returnRecordsWithNoMetrics to include ads with no activity, returnRowTotals for subtotal rows, and returnGrandTotals for a grand-total row.
custom_reports_create
params_unexplained
Create an Impression Share (Share of Voice) report asynchronously; returns a reportId you poll with custom_reports_get until state=COMPLETED. name labels the report (max 50 chars); granularity aggregates DAILY or WEEKLY (dateRange is needed when WEEKLY); selector filters to specific countryOrRegion or searchTerm values and controls fields, ordering, and pagination.
custom_reports_get
params_unexplained
Fetch a single Impression Share report by reportId (the identifier returned by custom_reports_create). Poll the state until COMPLETED, then read the report rows exposing rank, impressionShare, lowImpressionShare, highImpressionShare, and searchPopularity.
custom_reports_list
no_return_description
List all Impression Share reports created in the org, paginated with limit (max 1000) and offset. Returns report summaries including their reportId and current state so you can poll a completed one with custom_reports_get.
budget_orders_create
params_unexplained no_return_description
Create a budget order (LOC accounts only), wrapping Apple's {bo, orgIds} envelope. name labels the order; budget holds the amount and 3-letter currency; startDate/endDate bound the flight (YYYY-MM-DD); supplySources selects where the budget applies (e.g. APPSTORE_SEARCH_RESULTS); billingEmail, primary buyer, clientName, and orderNumber are record-keeping fields; orgIds must contain exactly one org. Returns the created budget order with its ID.
budget_orders_get
name_restates_behavior params_unexplained no_return_description
Fetch a single budget order by its budgetOrderId to inspect its budget, start/end dates, status, and buyer details. Returns the budget order object.
budget_orders_list
name_restates_behavior no_return_description
List all budget orders in the org, paginated with limit (max 1000) and offset. Returns budget order summaries including their IDs and statuses so you can drill into a specific one with budget_orders_get.
budget_orders_update
params_unexplained no_return_description
Update an existing budget order identified by budgetOrderId, passing the editable fields under bo (name, budget, dates, buyers, supplySources); status is read-only and v5 has no delete endpoint for budget orders. orgIds holds the single target org. Returns the updated budget order.
product_page_reasons_find
no_return_description
Find ad-creative rejection reasons using a selector - filter on fields such as adamId, productPageId, assetGenId, countryOrRegion, languageCode, supplySource, or reasonLevel to audit why a creative was rejected by Apple's review team. Returns the matching rejection-reason records.
product_page_reasons_get
params_unexplained no_return_description
Fetch a single ad-creative rejection reason by its productPageReasonId to see the explanation for a creative rejection. Returns the rejection-reason object.
apple_search_ads_request
params_unexplained no_return_description
Escape hatch that calls any Apple Search Ads v5 endpoint with the given HTTP method (GET/POST/PUT/DELETE/PATCH) and path; authentication and org context are handled for you. method selects the HTTP verb, query adds query-string params, and body supplies the request payload when the endpoint needs one. Set noOrgContext for endpoints like /me or /acls. Returns the endpoint's raw JSON response. Use only if no dedicated tool covers the endpoint you need.

Selection evidence

Confusable tool pairs.

10 pairs where similar names or overlapping descriptions may send an agent toward the wrong tool.

Tool A Tool B Confidence Why they collide
targeting_keywords_delete targeting_keywords_delete_single high A task like "delete the targeting keyword with id 42" fits both tools, as the bulk tool accepts a one-element list and the single tool takes one keywordId; nothing in the descriptions forces the single variant.
adgroup_negative_keywords_delete campaign_negative_keywords_delete high "Delete these negative keywords" is level-ambiguous; identical keyword text/id can exist at both ad-group and campaign level, so scope must be inferred from the task.
adgroup_negative_keywords_get campaign_negative_keywords_get high "Get/ fetch negative keyword 77" does not state whether it is ad-group-level or campaign-level; both tools share the same shape (campaignId + keywordId).
adgroup_negative_keywords_list campaign_negative_keywords_list high "List the negative keywords for this campaign" is ambiguous about level; without an adGroupId the agent may default to either listing tool and return the wrong scope.
adgroup_negative_keywords_update campaign_negative_keywords_update high "Pause/update negative keyword X" does not specify level; both are bulk updates keyed by campaignId and keyword id, so the agent can pick the wrong entity level.
adgroup_negative_keywords_find campaign_negative_keywords_find medium "Find negative keywords matching text X" is level-ambiguous and both accept a very similar selector; the task must mention adGroup vs campaign scope to disambiguate.
reports_adgroups reports_keywords_in_adgroup medium "Give me the performance report for ad group 123" fits both: reports_adgroups is the ad-group-level report (but has no adGroupId filter) while reports_keywords_in_adgroup is keyword-scoped and accepts adGroupId, so the agent can select the wrong granularity.
reports_search_terms_in_campaign reports_search_terms_in_adgroup medium A bare "search terms report" task is ambiguous about scope; both are named identically and share campaignId, differing only by an optional adGroupId that the task may omit.
apps_locale_details cpp_locale_details medium "Get the localized product page details for this app" is ambiguous between the app's default product page and a Custom Product Page; both key off adamId and differ mainly by productPageId.
product_page_reasons_find product_page_reasons_get medium "Get the rejection reason for this creative" fits both the selector-based find (which audits rejection reasons) and the single-by-Id get; the agent must decide whether a known reason id exists.

Compare the field

One score is useful.
The evidence makes it actionable.

Back to the leaderboard