Squirrelscan vs Meta Ads MCP

A side-by-side look at the Squirrelscan and Meta Ads MCP MCP servers: transport, auth, tools, quality signals, and the config to install either one in Claude, Cursor, Windsurf or VS Code.

Squirrelscan logoSquirrelscanMarketing · Remote HTTP/SSEAudit websites for SEO, performance, security, accessibility and agent experience issues.63GoodAuth: not declaredMeta Ads MCP logoMeta Ads MCPMarketing · Local subprocess (stdio)Meta Ads automation that just works. Trusted by 10,000+ businesses to analyze performance, test creatives, optimize spend, and scale results — simply and reliably.60GoodAuth: oauth 2.0

Verdict: choose Squirrelscan for marketing work over a hosted endpoint; choose Meta Ads MCP for marketing as a local process. You can also run both side by side in one mcpServers block.

Which MCP server should you choose?

Squirrelscan logoChoose Squirrelscan when

  • You need dedicated capabilities in the Marketing domain.
  • You prefer a remote HTTP/SSE transport.
  • You want tools like run_audit, get_audit_status, list_audits.
Explore Squirrelscan

Meta Ads MCP logoChoose Meta Ads MCP when

  • You need dedicated capabilities in the Marketing domain.
  • You prefer a local stdio subprocess transport.
  • Its auth model fits you: OAuth 2.0.
  • You want tools like get_ad_accounts, get_account_info, get_campaigns.
Explore Meta Ads MCP

Feature & specification comparison

Rows where the two servers differ are marked.

SpecSquirrelscan logoSquirrelscanMeta Ads MCP logoMeta Ads MCP
CategoryMarketingMarketing
Quality signal63/100 · Good60/100 · Good
Install commandConfirmedConfirmed
TransportRemote HTTP/SSELocal subprocess (stdio)
AuthNot declaredOAuth 2.0
PricingNot declaredFreemium
Required env varsNone listedNone listed
Verified ownerCommunity listingCommunity listing
GitHub stars1011,319
Directory activity 0 views 0 installs 0 upvotes 8 views 0 installs 0 upvotes
Tools2735

Tools & capabilities

Squirrelscan tools 27

  • run_auditRun a cloud audit of a website (crawl + 300+ rule analysis + report). Credits are spent as the audit runs (pay-as-you-go). The dry run is optional: pass confirm: true on the first call to start straight away. Without confirm, an audit whose estimate is over the org's auto-run threshold comes back as status "confirmation_required" with the estimate to show the user; one at or under the threshold just starts. That response carries "sufficient": true means the organization can pay, so show the estimate and call again with confirm: true; false means it cannot, and the response then carries the cost, the balance, the credit reset date and an upgrade_url for that organization. Relay those, and do NOT retry with confirm: true, which is refused. Pick the audit with level: quick (25 pages from the seed and sitemaps with external link checks, without the cloud checks), surface (100 pages, one per URL pattern, with the cloud checks and external link checks) or full (500 pages, every page it can reach; Pro and Team only: on the Free plan a full request crawls one page per URL pattern instead, up to the plan's page ceiling, and says so in level_notice). Without level the website's own level applies (a new website starts on surface). Every level checks external links, quick included, unless the website's external link setting or this run's external_links turns them off; each distinct external link the cloud verifies costs 1 credit (a link it cannot verify costs nothing), at most one per page of the page budget, and the estimate includes it. Use max_pages to size the crawl (max_pages: 1 audits just the entry URL, the cheapest run). Audits are asynchronous and take minutes: poll get_audit_status with the returned run_id, then fetch results with get_report. The website is registered automatically on first audit. A started run carries a schedule field describing the site's recurring audits: when state is active say so, since each of those runs costs credits, and settings_url is where the user turns them off. pending_first_audit: true means they switch on by themselves after this site's first successful audit, estimated_cost is the most they spend per run, week and month, and update_website_settings with schedule "off" turns them off.
  • get_audit_statusPoll a running audit by run_id (from run_audit or list_audits). Status queued/pending/running means keep polling (every 15-30 seconds): the response includes a progress field (phase, message, page/link counts) when available. Status queued means cloud audits are busy and the run is waiting for a slot: it starts by itself, and its queue field gives its position, a rough eta_minutes (null when unknown) and a message to pass on. Do not start the audit again. Status completed means the report is ready: call get_report with the same run_id. Status failed/cancelled includes the error and completion reason. A failed run also carries failure_reason_code (one of dns, tls, connection, timeout, http_4xx, http_5xx, redirect, robots, audit_timeout, stalled, unknown) and failure_next_step, so you can act on the cause rather than parsing the error sentence, plus failure_diagnostics: attribution (target_site, squirrelscan, mixed or unknown; never guess past it), category, stage, the observations that support it, what remains uncertain, and the bounded list of fetch attempts; legacy is true when the run predates recorded evidence. A cancelled run has none of these, because it was stopped rather than defeated by the site. Once the run has a report, health_score and issues_found are read from that published report, so they equal get_report's summary.healthScore and its failed+warnings (#1700). health_score is null for a blocked or unreachable site: that audit has no meaningful grade. A completed run also carries a schedule field describing the website's recurring audits: state is off, active, capped, unschedulable or paused, cadence_label reads as a sentence, settings_url is where the user changes the cadence or turns it off, and cap says how many scheduled sites the plan funds and how many are spent. estimated_cost is the most those runs spend per run, week and month. Recurring audits turn on by themselves after a site's first successful audit and each run costs credits, so pass that on rather than leaving the user to discover the charge.
  • list_auditsList the organization's audit runs: currently active (queued/pending/running; queued means waiting for a free cloud slot, and it starts by itself) plus recent runs of any status. Pass website_id (from list_websites) to get one website's full audit history, oldest runs included, and page through it with limit/offset using the returned total/has_more. This is the way to reach the preserved reports of a soft-deleted website: its runs still list under its old website_id. total/has_more describe the `recent` array only, and `recent` already includes any queued/pending/running run, so `active` is a live view of those same runs and not extra items to add to the count. Use the run ids with get_audit_status or get_report, and website ids with list_issues. Numbers on a recent run come from its published report (#1700): health_score is that report's overall score (the same value as get_report's summary.healthScore; null when the site was blocked or down, which earns no grade) and issues_found is the report's failing + warning checks (its summary.failed + summary.warnings). health_score is the metric to compare run over run: it is the only one rescored consistently across the whole site. Do NOT compare issues_found with the `open` count from list_issues: the issue tracker folds a rule failing on many pages into one open issue and carries issues forward until they are re-checked, so its count is legitimately much smaller and moves independently.
  • list_websitesList websites the organization has audited, with their latest run status, health score, and owned/prospect kind. Each row carries last_run_id (the latest run, any status) and last_report_run_id / last_report_id (the latest completed run whose report has not been deleted). Pass last_report_run_id to get_report to read a website's newest report without knowing a run id in advance, or list_audits with website_id for its full history. Use the website_id with list_issues/get_issue. Websites registered but never audited do not appear; run_audit or add_website registers a new one. Ephemeral one-shot audits never appear. Returns total/has_more for pagination. Filter by kind to separate sites the user runs from one-off prospect audits: kind: "prospect" returns ONLY sites explicitly marked as such, so it is the safe way to build a bulk-delete list.
  • add_websiteRegister a website with the organization without running an audit (run_audit registers automatically, so this is only needed to set a site up ahead of time). Returns the website_id; idempotent per domain, so calling it again returns the existing website. Pass kind to classify it as owned or prospect up front. On a plan with scheduled audits, a NEW site is registered with recurring weekly audits already on, and each of those runs costs credits: tell the user before calling this; update_website_settings with schedule "off" turns them off. The result's scheduled_audits field reports what was actually set.
  • delete_websiteDelete a website from the organization (soft delete: past audits, reports, and issues are preserved, and published report links keep working). Frees a slot under the plan's website limit. Re-adding the same domain later registers a fresh website with a new website_id. Call once without confirm to see what will happen; call again with confirm: true to delete. To remove many sites at once, use delete_websites.
  • delete_websitesDelete up to 50 websites in one call, for cleaning up a dashboard that has filled with one-off or prospect audits. Same soft delete as delete_website (past audits, reports, and issues are preserved, published report links keep working, slots are freed) and the same two-step confirm: call once without confirm to see the domain behind every id, then again with confirm: true. EVERY result echoes the domain, so read them back to the user before and after: an id is not a name, and this is the tool most likely to be pointed at the wrong list. The preview also shows each site's kind and an owned_count: unclassified sites read as owned, so an 'owned' entry in a list you built from prospects is the clearest sign the wrong ids were assembled. Ids that do not resolve are reported per-id, never as a whole-call failure. To pick the ids, list_websites with kind: "prospect" returns only sites explicitly marked disposable.
  • get_website_settingsRead one website's stored audit settings: its audit level (quick, surface, full or custom) with the page cap, crawl strategy, rendering and external link checks it sets, the crawl depth, crawl scope, include/exclude patterns and internal link checks, whether broken link alerts are on (broken_link_alerts, Pro and Team), and its recurring-audit schedule. runs_as is how the next audit runs on the organization's plan (a level or page cap the plan does not run is held to the plan there, with a notice) and credits_per_run_max is the most one audit of it can cost. schedule says whether recurring audits are on; pending_first_audit: true means they switch on by themselves after the site's first successful audit, and estimated_cost is the most they spend per run, week and month.
  • update_website_settingsChange one website's stored audit settings: level, max_pages, render, external_links, max_depth, scope, include_patterns, exclude_patterns and internal_links, broken_link_alerts (Pro and Team; switching it off is allowed on every plan), and schedule (off, daily, weekly or monthly) for its recurring audits. Omitted fields are left unchanged. Values are checked the same way the dashboard's settings are and held to the organization's plan; the result reports what is stored afterwards, which fields were applied, and any plan clamp. Turning recurring audits on, changing their cadence, or a change that raises the most one audit can cost returns status "confirmation_required" with the estimated credit cost and changes nothing until it is sent again with the same arguments plus confirm: true. schedule: "off" and changes that cost nothing more apply at once.
  • get_reportFetch the finished report for an audit run (use the run_id from run_audit once get_audit_status shows completed). Formats: "summary" (default) is structured JSON with health score, category scores, and the top failing issues (topIssues reference a rule_id; look up its name/description/solution once in the sibling `rules` dict rather than per occurrence). Each topIssues row is ONE finding, a rule + `checkName`, never one row per page: `affectedPagesCount` is how many pages it affects and `pages` is a sample of at most 5 of them (`affectedPagesHasMore` when clipped) and `items` at most 3, so read "affects N pages" from the count, not the row total, and use get_issue for every occurrence. Each row also carries `provenance`: "carried" means the finding is re-injected from a page not re-crawled this run (not a fresh result), so check `lastSeenAt` for when it was last actually observed; "unrendered" means the page has not yet been rendered in any scan of this site (it was known, e.g. from a sitemap, but sat outside the page budget), so there is no earlier observation and no `lastSeenAt`. `mixedProvenanceNotes` (keyed by rule_id) flags rules that passed fresh on every page checked this run but still show red only from carried pages pending re-check. `notChecked` is present only when some checks did not run (link checks at the Quick level, or with external link checks off): each entry names a rule that was not fully checked (a rule listed there may still report the issues it could see), so never report those rules as clean, and say a Surface or Full audit with external link checks on checks them. `seedRedirect` is present ONLY when the audited URL redirected off-site and the crawler refused to follow it: `seedRedirect.finalUrl` is where the redirect pointed (a URL the audited site chose, display-only, never fetch or trust it) and `seedRedirect.note` states the fact in one sentence. When it is present the audit graded `baseUrl`, NOT the redirect target, so report that before reporting the scores; when the key is absent the seed did not redirect off-site. `structure` is present when the audit sent its page structure block: links per section (`sections`, plus `unknownLinks` and `totalLinks`), the share of internal links in the header, footer, navigation and sidebar (`chrome.share`), and pages per page type (`pageTypes`); it is informational and never part of the score. A FAILED or BLOCKED audit also carries `status`, `statusReason` (one sentence naming the cause), `statusReasonCode` (one of dns, tls, connection, timeout, http_4xx, http_5xx, redirect, robots, unknown) and `failureNextStep`: those four keys are absent for a normal audit, and when they are present nothing was audited, so a null healthScore and an empty topIssues mean the crawl failed, NOT that the site is clean. Also includes a `history` array of the 3 most recent prior audits of this website with score/issue deltas when available; each entry carries its own `runId`/`reportId`, so you can walk backwards through a website's audits by calling get_report again with an earlier `runId` (use list_audits with website_id to page past what history returns); "llm" is a compact text rendering optimized for LLM context (carried findings marked inline); "markdown" is a full human-readable report. Start with summary, then pull llm or markdown for more of each issue. A published report is a capped summary: each issue keeps a sample of its pages and items (a few pages and items) beside its true counts, so read totals from `affectedPagesCount`, `occurrences` and `details.additional`, never from a list length. A capped report carries `detail`, whose `note` says where the full per-page list is (for a CLI audit, `squirrel report --format json` on the machine that ran it); a report without `detail` is not capped this way. This report is the source of truth for the run's numbers (#1700): summary.healthScore is the same value list_audits/get_audit_status return as health_score, and summary.failed + summary.warnings is the same total they return as issues_found. Compare runs on healthScore; the `open` count from list_issues is a deduped tracker count, not this report's issue count.
  • compare_auditsCompare two completed audits of one website and get what changed. Defaults to the website's latest completed audit against the one before it; pass base/head run ids to compare any two. Each finding gets one of seven kinds: new, resolved, worsened, improved, unchanged (both audits published it), still_open (still there, but one audit did not list it: the evidence store says it is open although the newer audit did not republish it, or the older audit's capped report sampled it out; `sampledOut` marks a clipped one, which is never counted as new; a sampled class that fails more often is listed in `sampledClassGrowth` and still counts as a change), or not_crawled (the page was not visited, so the issue is neither fixed nor still broken). resolutionEvidence says what backed the resolved verdicts; when it is "none" nothing is reported as fixed. Score movement is broken down by category. A website with only one audit returns first_run with an empty finding list; an audit where nothing moved returns changed: false. Unchanged, still-open and not-crawled rows are omitted unless requested.
  • list_issuesList a website's open audit issues (like a bug tracker: each issue is one failing rule with occurrences across pages, numbered per website). Returns issues sorted by severity. `total`, `returned` and `has_more` describe the issues matching YOUR filters; `website_totals` (by_status, active_by_severity: open and in_progress) always counts the whole website regardless of filters, so the two can differ (it replaces the earlier `severity_summary` and `status_counts` fields). Rows omit the long `recommendation` (use get_rule with the row's rule_id, or get_issue, or pass include_recommendation: true). Use the issue number with get_issue for full detail and comments. Filter by status/severity/category to narrow down. This is a per-website tracker spanning audits, not a per-run count: one rule failing on 600 pages is ONE issue here, and an issue stays open until an audit re-checks it. So the open count is expected to be far smaller than a run's issues_found (list_audits) or a report's failed+warnings, and the two are not comparable (#1700). To compare runs, use health_score from list_audits.
  • +15 more on the listing page

Meta Ads MCP tools 35

  • get_ad_accounts Get ad accounts accessible by a user. amount_spent and balance are returned in currency units (e.g. USD dollars), not cents. Args: access_token: Meta API access token (optional - will use cached token if not provided) user_id: Meta user ID or "me" for the current user limit: Maximum number of accounts to return (default: 200)
  • get_account_info Get detailed information about a specific ad account. Args: account_id: Meta Ads account ID (format: act_XXXXXXXXX) access_token: Meta API access token (optional - will use cached token if not provided) fields: Optional comma-separated Graph API fields to return. When provided, replaces the default field set. Useful for fetching extras like funding_source_details, spend_cap, is_prepay_account, min_daily_budget, disable_reason, capabilities. Default fields: id, name, account_id, account_status, amount_spent, balance, currency, age, business_city, business_country_code, timezone_name. For prepaid accounts (is_prepay_account=true, common in Brazil), the Business Manager "available balance" is the sum of funding_source_details STORED_BALANCE entries plus coupons — the `balance` field alone is the amount due to be billed, not the available pre-paid funds.
  • get_campaigns Get campaigns for a Meta Ads account with optional filtering. Note: By default, the Meta API returns a subset of available fields. Other fields like 'effective_status', 'spend_cap', 'budget_remaining', 'promoted_object', 'source_campaign_id', etc., might be available but require specifying them in the API call (currently not exposed by this tool's parameters). Args: account_id: Meta Ads account ID (format: act_XXXXXXXXX) access_token: Meta API access token (optional - will use cached token if not provided) limit: Maximum number of campaigns to return (default: 10) status_filter: Filter by effective status (e.g., 'ACTIVE', 'PAUSED', 'ARCHIVED'). Maps to the 'effective_status' API parameter, which expects an array (this function handles the required JSON formatting). Leave empty for all statuses. objective_filter: Filter by campaign objective(s). Can be a single objective string or a list of objectives. Valid objectives: 'OUTCOME_AWARENESS', 'OUTCOME_TRAFFIC', 'OUTCOME_ENGAGEMENT', 'OUTCOME_LEADS', 'OUTCOME_SALES', 'OUTCOME_APP_PROMOTION'. Examples: 'OUTCOME_LEADS' or ['OUTCOME_LEADS', 'OUTCOME_SALES']. Leave empty for all objectives. after: Pagination cursor to get the next set of results
  • get_campaign_details Get detailed information about a specific campaign. Note: This function requests a specific set of fields ('id,name,objective,status,...'). The Meta API offers many other fields for campaigns (e.g., 'effective_status', 'source_campaign_id', etc.) that could be added to the 'fields' parameter in the code if needed. Args: campaign_id: Meta Ads campaign ID access_token: Meta API access token (optional - will use cached token if not provided)
  • create_campaign Create a new Facebook or Instagram ad campaign in a Meta Ads account. Use this to start a new campaign with an ODAX objective (OUTCOME_LEADS, OUTCOME_SALES, OUTCOME_AWARENESS, OUTCOME_TRAFFIC, OUTCOME_ENGAGEMENT, OUTCOME_APP_PROMOTION), pick CBO (campaign budget optimization) or ABO (ad-set-level budgets), and set bid strategy, spend cap, and special ad categories. This is the first step of the campaign group → ad set → ad hierarchy on Meta. Returns the new campaign id. Also known as: create campaign, new campaign, make campaign, campaign group, ABO campaign, CBO campaign. Note: Campaigns do not support start_time for scheduling — set start_time on the ad set instead. Args: account_id: Meta Ads account ID (format: act_XXXXXXXXX) name: Campaign name objective: Campaign objective (ODAX, outcome-based). Must be one of: OUTCOME_AWARENESS, OUTCOME_TRAFFIC, OUTCOME_ENGAGEMENT, OUTCOME_LEADS, OUTCOME_SALES, OUTCOME_APP_PROMOTION. Note: Legacy objectives like BRAND_AWARENESS, LINK_CLICKS, CONVERSIONS, APP_INSTALLS, etc. are not valid for new campaigns and will cause a 400 error. Use the outcome-based values above (e.g., BRAND_AWARENESS → OUTCOME_AWARENESS). access_token: Meta API access token (optional - will use cached token if not provided) status: Initial campaign status (default: PAUSED) special_ad_categories: List of special ad categories if applicable daily_budget: Daily budget in account currency (in cents) as a string (only used if use_adset_level_budgets=False) lifetime_budget: Lifetime budget in account currency (in cents) as a string (only used if use_adset_level_budgets=False) buying_type: Buying type (e.g., 'AUCTION') bid_strategy: Bid strategy (default: LOWEST_COST_WITHOUT_CAP). Must be one of: 'LOWEST_COST_WITHOUT_CAP', 'LOWEST_COST_WITH_BID_CAP', 'COST_CAP', 'LOWEST_COST_WITH_MIN_ROAS'. WARNING: If you use LOWEST_COST_WITH_BID_CAP or COST_CAP, all child ad sets will require bid_amount to be set. bid_cap: Bid cap in account currency (in cents) as a string spend_cap: Spending limit for the campaign in account currency (in cents) as a string campaign_budget_optimization: Whether to enable campaign budget optimization (only used if use_adset_level_budgets=False) ab_test_control_setups: Settings for A/B testing (e.g., [{"name":"Creative A", "ad_format":"SINGLE_IMAGE"}]) use_adset_level_budgets: If True, budgets will be set at the ad set level instead of campaign level (default: False)
  • update_campaign Update an existing campaign in a Meta Ads account. Note: Campaigns do not support start_time for scheduling — set start_time on the ad set instead. Migrating CBO (Advantage Campaign Budget) → ABO (ad set level budgets): Pass `adset_budgets` with one entry per ad set in the campaign. Meta atomically removes the campaign-level budget and assigns budgets at the ad set level in a single call. This is Meta's documented mechanism — the legacy `use_adset_level_budgets=true` flag attempts to clear `daily_budget`/`lifetime_budget` but Meta silently ignores the empty values, so the migration does not persist. Args: campaign_id: Meta Ads campaign ID access_token: Meta API access token (optional - will use cached token if not provided) name: New campaign name status: New campaign status (e.g., 'ACTIVE', 'PAUSED') special_ad_categories: List of special ad categories if applicable daily_budget: New daily budget in account currency (in cents). lifetime_budget: New lifetime budget in account currency (in cents). bid_strategy: New bid strategy bid_cap: New bid cap in account currency (in cents) as a string spend_cap: New spending limit for the campaign in account currency (in cents) as a string campaign_budget_optimization: Enable/disable campaign budget optimization objective: New campaign objective (Note: May not always be updatable) use_adset_level_budgets: Deprecated for CBO → ABO migration — use `adset_budgets` instead. Kept for backwards compatibility; sends empty `daily_budget`/ `lifetime_budget` which Meta silently ignores in most cases. adset_budgets: List of `{"adset_id": "...", "daily_budget": <cents>}` objects. Use to migrate from CBO to ABO: Meta removes the campaign-level Advantage budget and assigns the provided daily budgets at the ad set level in one atomic call. Example: [{"adset_id": "1234", "daily_budget": 5000}, {"adset_id": "5678", "daily_budget": 7000}]
  • get_adsets Get ad sets for a Meta Ads account with optional filtering by campaign. Args: account_id: Meta Ads account ID (format: act_XXXXXXXXX) access_token: Meta API access token (optional - will use cached token if not provided) limit: Maximum number of ad sets to return (default: 10) campaign_id: Optional campaign ID to filter by
  • get_adset_details Get detailed information about a specific ad set. Args: adset_id: Meta Ads ad set ID access_token: Meta API access token (optional - will use cached token if not provided) Example: To call this function through MCP, pass the adset_id as the first argument: { "args": "YOUR_ADSET_ID" }
  • create_adset Create a new ad set in a Meta Ads account. Args: account_id: Meta Ads account ID (format: act_XXXXXXXXX) campaign_id: Meta Ads campaign ID this ad set belongs to name: Ad set name optimization_goal: Conversion optimization goal. Valid values depend on the campaign objective and destination_type. OUTCOME_ENGAGEMENT + destination_type=WEBSITE: OFFSITE_CONVERSIONS, LANDING_PAGE_VIEWS, LINK_CLICKS, IMPRESSIONS, REACH. OUTCOME_ENGAGEMENT + On Post (destination_type=ON_POST): POST_ENGAGEMENT, IMPRESSIONS, REACH. Also set promoted_object={page_id} at creation (immutable; without it create_ad fails with subcode 1885154). Do NOT use ON_AD here — ON_AD is the OUTCOME_LEADS instant-form destination and Meta rejects it for OUTCOME_ENGAGEMENT (subcode 1815715). OUTCOME_ENGAGEMENT + On Video (destination_type=ON_VIDEO): THRUPLAY, TWO_SECOND_CONTINUOUS_VIDEO_VIEWS. OUTCOME_ENGAGEMENT + On Event (destination_type=ON_EVENT): EVENT_RESPONSES, IMPRESSIONS, POST_ENGAGEMENT, REACH. OUTCOME_ENGAGEMENT + On Page (destination_type=ON_PAGE): PAGE_LIKES. OUTCOME_ENGAGEMENT + Messaging (MESSENGER/WHATSAPP/INSTAGRAM_DIRECT): CONVERSATIONS, LINK_CLICKS. OUTCOME_ENGAGEMENT "Profile and Page visits" (PROFILE_AND_PAGE_ENGAGEMENT with destination_type INSTAGRAM_PROFILE / FACEBOOK_PAGE / INSTAGRAM_PROFILE_AND_FACEBOOK_PAGE) is shown in Ads Manager but NOT supported via the Marketing API — Meta rejects every variant (code 100). Closest API-supported option is POST_ENGAGEMENT + ON_POST. OUTCOME_TRAFFIC + WEBSITE: LANDING_PAGE_VIEWS, LINK_CLICKS, IMPRESSIONS, REACH. OUTCOME_AWARENESS: REACH, IMPRESSIONS, AD_RECALL_LIFT, THRUPLAY. OUTCOME_LEADS: LEAD_GENERATION, QUALITY_LEAD (forms), QUALITY_CALL (calls), OFFSITE_CONVERSIONS, LINK_CLICKS (website). OUTCOME_SALES: OFFSITE_CONVERSIONS, VALUE, CONVERSATIONS, LINK_CLICKS, IMPRESSIONS, REACH. OUTCOME_APP_PROMOTION: APP_INSTALLS, APP_INSTALLS_AND_OFFSITE_CONVERSIONS, VALUE. billing_event: How you're charged (e.g., 'IMPRESSIONS', 'LINK_CLICKS') status: Initial ad set status (default: PAUSED) daily_budget: Daily budget in account currency (in cents) as a string. CBO NOTE: Do NOT set this if the parent campaign already has a budget (Campaign Budget Optimization / CBO mode). Meta only allows budgets at one level: either the campaign OR the ad set, not both. If the campaign has a daily_budget or lifetime_budget, omit this field — the ad set will automatically use the campaign budget. lifetime_budget: Lifetime budget in account currency (in cents) as a string. CBO NOTE: Do NOT set this if the parent campaign already has a budget (Campaign Budget Optimization / CBO mode). Omit this field when the campaign uses CBO — the ad set inherits the campaign budget automatically. targeting: Targeting specs (age, location, interests, etc). targeting_automation.advantage_audience defaults to 0 if not set (Meta API v24+ requirement). Set to 1 to enable Advantage+ Audience (requires age_max>=65). Use search_interests for interest IDs. bid_amount: Bid amount in account currency (in cents). REQUIRED for: LOWEST_COST_WITH_BID_CAP, COST_CAP, TARGET_COST. NOT USED by: LOWEST_COST_WITH_MIN_ROAS (uses bid_constraints instead). May also be required if the parent campaign's bid strategy requires it. bid_strategy: Bid strategy. Valid values: - 'LOWEST_COST_WITHOUT_CAP' (recommended) - no bid_amount required - 'LOWEST_COST_WITH_BID_CAP' - REQUIRES bid_amount - 'COST_CAP' - REQUIRES bid_amount - 'LOWEST_COST_WITH_MIN_ROAS' - REQUIRES bid_constraints with roas_average_floor, and optimization_goal='VALUE'. Does NOT use bid_amount. Note: 'LOWEST_COST' is NOT valid - use 'LOWEST_COST_WITHOUT_CAP'. Campaign-level bid strategy may constrain ad set choices. bid_constraints: Bid constraints dict. Required for LOWEST_COST_WITH_MIN_ROAS. Use {"roas_average_floor": <value>} where value = target ROAS * 10000. Example: 2.0x ROAS -> {"roas_average_floor": 20000} bid_adjustments: Bid multipliers per targeting dimension. Pass-through to Meta. Shape: {"user_groups": {"<dim>": {"<value>": <float>, "default": <float>}}} Dims: age, gender, user_os, device_platform, position_type, publisher_platform, user_bucket, home_location, locale, etc. Multipliers are floats, typically 0.0-1.0. Example: {"user_groups": {"user_os": {"iOS": 0.9, "Android": 0.7, "default": 1.0}}} NOTE: Writing bid_adjustments requires a Meta app capability that must be allowlisted. Apps without it get OAuthException (#3). start_time: Start time in ISO 8601 format (e.g., '2023-12-01T12:00:00-0800'). To schedule future delivery: set start_time to a future date and status=ACTIVE. Meta will show effective_status as SCHEDULED and automatically begin delivery at start_time. NOTE: Only ad set start_time controls delivery scheduling. Campaigns do not support start_time. end_time: End time in ISO 8601 format. Required when lifetime_budget is specified. dsa_beneficiary: DSA beneficiary for European compliance (person/org that benefits from ads). Required for EU-targeted ad sets along with dsa_payor. dsa_payor: DSA payor for European compliance (person/org paying for the ads). Required for EU-targeted ad sets along with dsa_beneficiary. promoted_object: For APP_INSTALLS: app config, required application_id + object_store_url. For OUTCOME_ENGAGEMENT On-Post (destination_type=ON_POST): set {"page_id": "<id>"} at creation — required for ads (else create_ad fails with subcode 1885154) and immutable afterward (cannot be added via update_adset). destination_type: Conversion location / where users go. Pass-through to Meta (no client-side validation). Common values: 'WEBSITE', 'WHATSAPP', 'MESSENGER', 'INSTAGRAM_DIRECT', 'APP', 'FACEBOOK', 'SHOP_AUTOMATIC'. OUTCOME_ENGAGEMENT on-asset locations: 'ON_POST' (post engagement; needs promoted_object={page_id}), 'ON_PAGE' (PAGE_LIKES), 'ON_EVENT', 'ON_VIDEO'. 'ON_AD' is the OUTCOME_LEADS instant-form destination — do NOT use it for OUTCOME_ENGAGEMENT (Meta rejects it with subcode 1815715). Also supports multi-channel combos like 'MESSAGING_MESSENGER_WHATSAPP'. is_dynamic_creative: Enable Dynamic Creative for this ad set. frequency_control_specs: Frequency cap specs. MUST be set at creation time — Meta makes this field immutable after the ad set is created (error 1815198). Only works with OUTCOME_AWARENESS campaigns + optimization_goal REACH or THRUPLAY. Example: [{"event": "IMPRESSIONS", "interval_days": 7, "max_frequency": 1}] multi_advertiser_ads: Set to 0 to opt out of Multi-Advertiser Ads, 1 to opt in. This is a TOP-LEVEL ad set parameter — do NOT put it inside the targeting object. regional_regulated_categories: List of regional regulated categories for the ad set. Required for ads targeting regulated regions (Taiwan, Australia, etc.). Valid values: TAIWAN_FINSERV, TAIWAN_UNIVERSAL, AUSTRALIA_FINSERV, INDIA_FINSERV, SINGAPORE_UNIVERSAL, THAILAND_UNIVERSAL. Example: ["TAIWAN_UNIVERSAL"] or ["TAIWAN_FINSERV", "TAIWAN_UNIVERSAL"] regional_regulation_identities: Dict of verified identity IDs for regional transparency compliance. Required when regional_regulated_categories is set. The identity IDs come from completing advertiser verification in Meta Business Settings. Keys depend on the categories declared: - TAIWAN_UNIVERSAL: taiwan_universal_beneficiary, taiwan_universal_payer - TAIWAN_FINSERV: taiwan_finserv_beneficiary, taiwan_finserv_payer - AUSTRALIA_FINSERV: australia_finserv_beneficiary, australia_finserv_payer - SINGAPORE_UNIVERSAL: singapore_universal_beneficiary, singapore_universal_payer Example: {"taiwan_universal_beneficiary": "<id>", "taiwan_universal_payer": "<id>"} attribution_spec: Attribution window specification for the ad set. Controls how conversions are attributed to ads. Default is 7-day click if not specified. Example for 1-day click: [{"event_type": "CLICK_THROUGH", "window_days": 1}] Example for 1-day click + 1-day view: [{"event_type": "CLICK_THROUGH", "window_days": 1}, {"event_type": "VIEW_THROUGH", "window_days": 1}] Valid event_type values: CLICK_THROUGH, VIEW_THROUGH. Valid window_days values: 1, 7, 28 (depends on event_type and optimization_goal). access_token: Meta API access token (optional - will use cached token if not provided)
  • update_adset Update an ad set with new settings including frequency caps and budgets. Args: adset_id: Meta Ads ad set ID name: New ad set name frequency_control_specs: Frequency control specs (e.g. [{"event": "IMPRESSIONS", "interval_days": 7, "max_frequency": 3}]) bid_strategy: Bid strategy. Valid values: - 'LOWEST_COST_WITHOUT_CAP' (recommended) - no bid_amount required - 'LOWEST_COST_WITH_BID_CAP' - REQUIRES bid_amount - 'COST_CAP' - REQUIRES bid_amount - 'LOWEST_COST_WITH_MIN_ROAS' - REQUIRES bid_constraints with roas_average_floor Note: 'LOWEST_COST' is NOT valid - use 'LOWEST_COST_WITHOUT_CAP'. bid_amount: Bid amount in cents. Required for LOWEST_COST_WITH_BID_CAP, COST_CAP, TARGET_COST. NOT USED by LOWEST_COST_WITH_MIN_ROAS (uses bid_constraints instead). bid_constraints: Bid constraints dict. Required for LOWEST_COST_WITH_MIN_ROAS. Use {"roas_average_floor": <value>} where value = target ROAS * 10000. Example: 2.0x ROAS -> {"roas_average_floor": 20000} bid_adjustments: Bid multipliers per targeting dimension. Pass-through to Meta. Shape: {"user_groups": {"<dim>": {"<value>": <float>, "default": <float>}}} See create_adset for full docs and dim list. NOTE: Writing requires a Meta app capability that must be allowlisted. status: Update ad set status (ACTIVE, PAUSED, etc.) targeting: Complete targeting specifications (replaces existing targeting) optimization_goal: Conversion optimization goal (e.g., 'LINK_CLICKS', 'CONVERSIONS', 'VALUE') daily_budget: Daily budget in account currency (in cents) lifetime_budget: Lifetime budget in account currency (in cents) is_dynamic_creative: Enable/disable Dynamic Creative for this ad set. WARNING: This field is immutable after ad set creation. Meta's API will return success but silently ignore the change. To change this, create a new ad set. start_time: Start time in ISO 8601 format (e.g., '2023-12-01T12:00:00-0800'). Use with status=ACTIVE to schedule the ad set for future delivery (effective_status will be SCHEDULED until start_time). end_time: End time in ISO 8601 format. Required when lifetime_budget is specified. dsa_beneficiary: DSA beneficiary for European compliance (person/org that benefits from ads). Required for EU-targeted ad sets along with dsa_payor. dsa_payor: DSA payor for European compliance (person/org paying for the ads). Required for EU-targeted ad sets along with dsa_beneficiary. multi_advertiser_ads: Set to 0 to opt out of Multi-Advertiser Ads, 1 to opt in. This is a TOP-LEVEL ad set parameter — do NOT put it inside the targeting object. regional_regulated_categories: List of regional regulated categories for the ad set. Required for ads targeting regulated regions (Taiwan, Australia, etc.). Valid values: TAIWAN_FINSERV, TAIWAN_UNIVERSAL, AUSTRALIA_FINSERV, INDIA_FINSERV, SINGAPORE_UNIVERSAL, THAILAND_UNIVERSAL. Set to null/empty to remove existing categories. regional_regulation_identities: Dict of verified identity IDs for regional transparency compliance. Required when regional_regulated_categories is set. Set individual keys to null to remove them. attribution_spec: Attribution window specification for the ad set. WARNING: Meta no longer supports updating attribution_spec after ad set creation (error 1504040). To change attribution windows, create a new ad set instead. This parameter is kept for compatibility but will be rejected by Meta's API. Valid event_type values: CLICK_THROUGH, VIEW_THROUGH. Valid window_days values: 1, 7, 28 (depends on event_type and optimization_goal). access_token: Meta API access token (optional - will use cached token if not provided)
  • get_ads Get ads for a Meta Ads account with optional filtering. Args: account_id: Meta Ads account ID (format: act_XXXXXXXXX) access_token: Meta API access token (optional - will use cached token if not provided) limit: Maximum number of ads to return (default: 10) campaign_id: Optional campaign ID to filter by adset_id: Optional ad set ID to filter by
  • get_ad_details Get detailed information about a specific ad. Args: ad_id: Meta Ads ad ID access_token: Meta API access token (optional - will use cached token if not provided)
  • +23 more on the listing page

Ready-to-paste client configs

Paste either block (or both) into your client config, e.g. claude_desktop_config.json or ~/.cursor/mcp.json. We only show a config when the install command is confirmed.

Squirrelscan

mcpServers (Claude Desktop / Cursor)
{
  "mcpServers": {
    "squirrelscan": {
      "url": "https://mcp.squirrelscan.com/mcp"
    }
  }
}

Meta Ads MCP

mcpServers (Claude Desktop / Cursor)
{
  "mcpServers": {
    "pipeboard-co-meta-ads-mcp": {
      "command": "uvx",
      "args": [
        "meta-ads-mcp"
      ]
    }
  }
}

Frequently asked questions

Squirrelscan is categorized under Marketing and uses a remote HTTP/SSE transport. Meta Ads MCP belongs to Marketing and uses a local stdio subprocess. Pick Squirrelscan for marketing work and Meta Ads MCP for marketing.

Copy the mcpServers block from the config section on this page into your client's config (for example claude_desktop_config.json or ~/.cursor/mcp.json), then fully restart the client.

Squirrelscan has no declared pricing — check the project's own site. Meta Ads MCP is listed as freemium — a free tier with paid plans. Auth: oauth 2.0. If a server needs environment variables such as API keys, define them under the env key in your client's MCP config.

Yes. MCP clients support multiple servers: add squirrelscan and pipeboard-co-meta-ads-mcp as separate keys in the same mcpServers object, and your assistant will call tools from either one.

On AllMCPs, Squirrelscan has a quality signal of 63/100 (Good) with 0 views, 0 installs and 101 GitHub stars. Meta Ads MCP has 60/100 (Good) with 8 views, 0 installs and 1,319 GitHub stars.