Skip to main content
Glama
Meta Ads MCP

Meta Ads MCP

by AbnerZx

Meta Ads MCP

An MCP server for the Meta Marketing API — built for people running real ad spend.

118 typed tools across Ads, Insights, Pixels/CAPI, Pages, Instagram, WhatsApp Business, Catalogs, Audiences, Leads and Billing — with the token kept out of your config files and a confirmation gate on every destructive call.

License: MIT Python 3.11+ MCP Graph API v21.0 Transports: stdio + HTTP


Why this one

There are several Meta Ads MCP servers out there. This one is built around a specific assumption: the person using it is spending real money, possibly on behalf of clients, and a mis-fired tool call costs more than an apology.

That shapes three decisions the code makes differently:

How most setups do it

How this one does it

Token storage

Pasted into a JSON config or .env that sits in plaintext on disk

OS credential store first (Windows Credential Manager / macOS Keychain via keyring), .env only as fallback

Destructive calls

The model calls delete_campaign and it deletes

confirm=True required — the refusal names the operation and the target, so the intent shows up in the transcript before anything happens

Errors

Meta's opaque code=3 / subcode=1487348 reaches you raw

Decoded into a message that says what to actually do

Plus a global MCP_READ_ONLY kill switch — useful when the server is reachable from a phone.


Related MCP server: facebook-ads-mcp-server

Quick start

Requirements: Python 3.11+, a Meta System User token, and an MCP client (Claude Code, Claude Desktop, Cursor…).

git clone https://github.com/AbnerZx/meta-ads-mcp.git
cd meta-ads-mcp
powershell -ExecutionPolicy Bypass -File .\install.ps1

The installer checks your Python, creates .venv, installs dependencies, prompts for your token (masked input, stored in Credential Manager, never written to disk), and prints the exact registration command to run next.

git clone https://github.com/AbnerZx/meta-ads-mcp.git
cd meta-ads-mcp
python3 -m venv .venv && source .venv/bin/activate
pip install -e .
python -m keyring set meta-ads-mcp access_token   # paste token when prompted

Then register it with your client:

claude mcp add meta-ads --scope user -- /path/to/.venv/bin/python /path/to/src/server.py

Restart the client. Tools appear as mcp__meta-ads__*.

Verify it works — ask your assistant:

"List my Meta ad accounts."


What's in it

118 tools across 12 modules:

Module

Tools

What it covers

ads

29

Campaigns, ad sets, ads — create, update, duplicate, pause, activate, delete, schedules, previews

catalog

12

Product catalogs, feeds, product sets, diagnostics

instagram

12

Media, insights, publishing (image/video), comment moderation

pages

11

Posts, insights, publishing, comment moderation

creatives

10

Link & carousel creatives, image/video upload, previews

whatsapp

9

Business accounts, phone numbers, message templates, sending

audiences

8

Custom & saved audiences, applying them to ad sets

pixels

7

Pixel management, stats, sharing, Conversions API events

insights

6

Reports, period comparison, async report lifecycle

billing

6

Spend, limits, funding source, invoices, cross-account summary

leads

5

Lead forms and lead retrieval

graph

3

graph_call escape hatch, pagination, token debugging

graph_call matters more than it looks. The Meta Marketing API is enormous and changes often. Rather than pretend 118 tools cover it, there's a typed escape hatch for any endpoint that isn't wrapped yet — so you're never blocked waiting on this repo.


Conventions

Worth knowing before your first call:

  • Ad account IDs are auto-prefixed with act_ — pass 123456 or act_123456, both work.

  • Budgets are integers in account-currency cents. R$ 10,00 → 1000. This is Meta's convention, not ours.

  • Destructive ops need confirm=True. Without it you get a blocked-operation error naming the target.

  • The token never travels through tool arguments. It's loaded once at startup from the credential store.


Security model

The threats this is designed against, and what handles each:

Asset

Risk if exposed

Control

Meta access token

Full control of every ad account the token can reach

OS credential store (encrypted at rest); never logged, never serialized, never returned by a tool

Server logs

Token leaking through a stack trace

Regex scrubbing in client.py; response bodies are not logged

Docker image

Secrets baked into build layers

.dockerignore excludes .env, .git, secrets

Public endpoint

Brute force, DNS rebinding

Bearer auth with constant-time compare, global rate limit, MCP_PUBLIC_HOSTS allowlist

Destructive calls

A campaign paused or deleted by mistake

confirm=True per call + MCP_READ_ONLY global switch

CAPI payloads

PII sent in the clear

SHA-256 hashing applied client-side before transmission

Found a vulnerability? See SECURITY.md — please don't open a public issue.

Getting a token

Use a System User token, not a personal one. It doesn't expire, it's revocable independently of your own account, and Meta gives you a per-user audit trail.

Business Manager → Business settings → Users → System users → Add → assign only the ad accounts and Pages you actually need → Generate new token.

Typical scopes: ads_management, ads_read, business_management, read_insights. Add pages_*, instagram_*, leads_retrieval, whatsapp_business_* or catalog_management only if you use those modules. Meta shows the token exactly once.


Configuration

All optional except the token. See .env.example.

Variable

Default

Purpose

META_ADS_TOKEN

—

Token, if you're not using the credential store

META_DEFAULT_BUSINESS_ID

—

Fallback Business Manager ID

META_API_VERSION

v21.0

Graph API version

META_REQUEST_TIMEOUT

60

Read timeout, seconds — Meta's validation paths can sit 20–40s

META_RATE_LIMIT_RPS

5

Local rate limiter

MCP_READ_ONLY

false

true blocks every write, regardless of confirm

MCP_PUBLIC_HOSTS

—

Host allowlist — required for public HTTP deploys

MCP_RATE_LIMIT_RPM

60

Per-minute cap on the HTTP transport


Remote deployment

The server speaks two transports: stdio for local clients, streamable-http for remote ones. The HTTP mode lets you reach your ad accounts from Claude.ai on mobile via a Custom Connector.

→ Full deployment guide (DEPLOY.md) — Fly.io walkthrough, bearer secret generation, secret rotation, and an incident-response checklist.

Short version:

fly launch --no-deploy --copy-config    # edit fly.toml first: app name + MCP_PUBLIC_HOSTS
fly secrets set MCP_BEARER_SECRET="..." META_ADS_TOKEN="..."
fly deploy

fly.toml ships with MCP_READ_ONLY = 'true'. Leave it that way until you've confirmed the deployment works.


Project layout

src/
├── server.py           # stdio entrypoint
├── http_server.py      # streamable-http transport, bearer auth, rate limiting
├── mcp_instance.py     # shared FastMCP instance + transport security
├── client.py           # Graph API client — retries, backoff, log scrubbing
├── auth.py             # token loading (credential store → env fallback)
├── safety.py           # confirmation gate, ID normalization, input validation
└── tools/              # 12 modules, 118 tools

Contributing

Issues and pull requests are welcome — see CONTRIBUTING.md. Especially useful: Graph API error subcodes you've hit that aren't decoded yet, and coverage gaps you're currently working around with graph_call.

License

MIT © Abner Ferreira

Not affiliated with or endorsed by Meta Platforms, Inc. "Meta", "Facebook", "Instagram" and "WhatsApp" are trademarks of Meta Platforms, Inc.

Available Tools

118 tools
ads_activate_adB

Activate (resume) a single ad.

Inputs: ad_id (required), confirm.

ads_activate_adsetB

Activate (resume) a paused ad set.

Inputs: adset_id (required), confirm.

ads_activate_campaignB

Activate (resume) a paused campaign.

Inputs: campaign_id (required), confirm.

ads_create_adA

Create an ad inside an ad set, referencing an existing ad creative.

Use creatives_create_* tools first to build the creative, then pass its ID here.

Inputs: name (required), adset_id (required), account_id (required), creative_id (required), +3 more.

ads_create_adsetA

Create an ad set under a campaign.

Args: account_id: Parent ad account. campaign_id: Parent campaign. daily_budget_cents: Daily budget in cents (use this OR lifetime_budget_cents). lifetime_budget_cents: Total budget for the entire run (requires end_time). optimization_goal: REACH, IMPRESSIONS, LINK_CLICKS, OFFSITE_CONVERSIONS, LANDING_PAGE_VIEWS, MESSAGES, LEAD_GENERATION, CONVERSATIONS, etc. billing_event: IMPRESSIONS (most common), LINK_CLICKS, etc. targeting: Full targeting JSON (geo, age, interests, custom audiences). promoted_object: For conversion campaigns: {"pixel_id": ..., "custom_event_type": ...}. destination_type: WEBSITE, APP, MESSENGER, INSTAGRAM_DIRECT, ON_AD, etc. start_time / end_time: ISO 8601 timestamps. adset_schedule: Day-parting blocks. Each block: {"days": [0..6], "start_minute": 0..1440, "end_minute": 0..1440, "timezone_type": "USER"|"ADVERTISER"}. Requires pacing_type=["day_parting"] AND lifetime_budget_cents (not daily). Tip: use ads_set_adset_schedule for an ergonomic builder. pacing_type: ["standard"] (default) or ["day_parting"] when adset_schedule is set. attribution_spec: e.g. [{"event_type":"CLICK_THROUGH","window_days":7}]. frequency_control_specs: e.g. [{"event":"IMPRESSIONS","interval_days":7,"max_frequency":3}].

Inputs: name (required), targeting (required), account_id (required), campaign_id (required), +16 more.

ads_create_campaignA

Create a new campaign. Defaults to PAUSED for safety.

Args: account_id: Ad account ID. name: Campaign name. objective: One of OUTCOME_AWARENESS, OUTCOME_TRAFFIC, OUTCOME_ENGAGEMENT, OUTCOME_LEADS, OUTCOME_APP_PROMOTION, OUTCOME_SALES. status: ACTIVE or PAUSED (default PAUSED — safer). special_ad_categories: ["HOUSING"], ["EMPLOYMENT"], ["CREDIT"], ["ISSUES_ELECTIONS_POLITICS"], or [] for none. daily_budget_cents: Daily budget in account currency cents (e.g., 1000 = R$10). lifetime_budget_cents: Alternative to daily. bid_strategy: LOWEST_COST_WITHOUT_CAP, LOWEST_COST_WITH_BID_CAP, COST_CAP. buying_type: AUCTION (default) or RESERVED. confirm: Required True to actually create.

Inputs: name (required), objective (required), account_id (required), status, +6 more.

ads_delete_adC

Delete a single ad permanently.

Inputs: ad_id (required), confirm.

ads_delete_adsetA

Delete an ad set. Cascades to all ads under it.

Inputs: adset_id (required), confirm.

ads_delete_campaignA

Delete a campaign permanently. Cascades to all ad sets and ads under it.

Inputs: campaign_id (required), confirm.

ads_duplicate_adC

Duplicate a single ad.

Inputs: ad_id (required), confirm, status_option, rename_options.

ads_duplicate_adsetB

Duplicate an ad set, optionally including all ads inside it.

Inputs: adset_id (required), confirm, deep_copy, status_option, +1 more.

ads_duplicate_campaignA

Duplicate a campaign with optional deep copy of all ad sets and ads.

Args: deep_copy: If True, copies all ad sets and ads under the campaign. status_option: PAUSED (recommended) or ACTIVE for the copy. rename_options: Optional dict {"rename_strategy": "DEEP_RENAME", "rename_suffix": " - copy"}.

Inputs: campaign_id (required), confirm, deep_copy, status_option, +1 more.

ads_get_adB

Get full details of a single ad including creative.

Inputs: ad_id (required).

ads_get_ad_accountA

Get detailed info for a single ad account, including spending and limits.

Inputs: account_id (required).

ads_get_ad_previewB

Get a renderable HTML preview of an ad.

Common ad_format values: MOBILE_FEED_STANDARD, DESKTOP_FEED_STANDARD, INSTAGRAM_STANDARD, INSTAGRAM_STORY, FACEBOOK_STORY_MOBILE, AUDIENCE_NETWORK_OUTSTREAM_VIDEO.

Inputs: ad_id (required), ad_format.

ads_get_adsetA

Get full details of a single ad set, including targeting and dayparting (adset_schedule).

Inputs: adset_id (required).

ads_get_adset_scheduleA

Read an ad set's day-parting schedule, decoded into human-readable form.

Returns both the raw adset_schedule (Meta format) and a decoded view with Portuguese day names and HH:MM times. Returns enabled: False when no day-parting block is set (ad set runs 24/7 inside its start/end window).

Inputs: adset_id (required).

ads_get_campaignB

Get full details of a single campaign.

Inputs: campaign_id (required).

ads_list_ad_accountsB

List all ad accounts the System User can access.

Returns name, account_id, status, currency, owning business.

Inputs: limit.

ads_list_adsA

List ads under an ad account, campaign, or ad set.

Args: parent_id: act_... for an ad account, or a campaign/adset ID. status_filter: Optional effective_status filter.

Inputs: parent_id (required), limit, status_filter.

ads_list_adsetsA

List ad sets under an ad account or under a specific campaign.

Args: parent_id: Either an ad account ID (act_...) or a campaign ID. status_filter: Optional effective_status filter.

Inputs: parent_id (required), limit, status_filter.

ads_list_campaignsA

List campaigns of an ad account.

Args: account_id: Ad account ID (with or without act_ prefix). status_filter: Optional list of effective_status values (ACTIVE, PAUSED, ARCHIVED, DELETED, CAMPAIGN_PAUSED, etc.). Defaults to all. limit: Page size (max 100 per page; use cursor to paginate).

Inputs: account_id (required), limit, status_filter.

ads_pause_adC

Pause a single ad.

Inputs: ad_id (required), confirm.

ads_pause_adsetC

Pause an ad set.

Inputs: adset_id (required), confirm.

ads_pause_campaignA

Pause a campaign (stops delivery, preserves learning).

Inputs: campaign_id (required), confirm.

ads_set_adset_scheduleA

Set day-parting on an ad set with an ergonomic format.

Each block in schedule accepts EITHER:

  • Raw Meta format: {"days": [0..6], "start_minute": int, "end_minute": int, "timezone_type": "USER"}

  • Friendly format: {"days": ["seg","ter",...] OR [1,2,...], "start_time": "18:00", "end_time": "23:00", "timezone_type": "USER"} # default USER

Day aliases accepted (case-insensitive): Portuguese: dom seg ter qua qui sex sab (also "domingo", "terça"...) English: sun mon tue wed thu fri sat (also "monday"...) Numeric: 0–6 (0=Sunday)

Requirements (Meta API):

  • Ad set must use LIFETIME budget (not daily). Convert first if needed.

  • This tool automatically sets pacing_type=["day_parting"] unless you override via the pacing_type arg.

Pass an empty list to remove dayparting (return to 24/7).

Example: ads_set_adset_schedule( "12345", schedule=[{"days":["seg","qua","sex"],"start_time":"18:00","end_time":"23:00"}], confirm=True, )

Inputs: adset_id (required), schedule (required), confirm, pacing_type.

ads_update_adA

Update an ad's name, status, or swap its creative.

Inputs: ad_id (required), name, status, confirm, +1 more.

ads_update_adsetA

Update fields on an existing ad set.

NOTE: Changing budget, bid, targeting, or optimization restarts learning.

Day-parting (adset_schedule) requires the ad set to use lifetime budget + pacing_type=["day_parting"]. For an ergonomic builder, use ads_set_adset_schedule.

Inputs: adset_id (required), name, status, confirm, +15 more.

ads_update_campaignB

Update fields on an existing campaign.

Changing objective restarts learning AND may force-unpublish ad sets that aren't compatible with the new objective.

Inputs: campaign_id (required), name, status, confirm, +9 more.

audiences_apply_to_adsetA

Copy targeting from a saved audience to a live adset.

This is the verified workaround for the saved_audiences write capability gate: the adset endpoint accepts the same targeting object under standard ads_management scope, so we just pull the targeting from the saved audience and POST it on the adset.

Args: saved_audience_id: e.g. 120241644369970701 adset_id: target adset to update merge: how to combine the saved audience's targeting with the adset's existing targeting: - "replace" (default): overwrite the adset's targeting entirely - "merge": shallow-merge — saved-audience keys override adset keys, anything else on the adset is preserved overrides: extra targeting keys to apply on top of the resulting object (e.g. {"age_min": 25}). Useful for per-adset tweaks without editing the saved audience. confirm: must be True (the adset targeting WILL be rewritten).

Inputs: adset_id (required), saved_audience_id (required), merge, confirm, +1 more.

audiences_create_savedA

Create a saved audience.

KNOWN LIMITATION: Meta returns code=3 ("Application does not have the capability to make this API call") unless the underlying App has Advanced Access for saved_audiences writes. Most MCP deployments do NOT have this. If you hit that error, use audiences_apply_to_adset() to apply targeting directly to an adset instead.

Inputs: name (required), targeting (required), account_id (required), confirm, +1 more.

audiences_delete_savedA

Permanently delete a saved audience.

KNOWN LIMITATION: also gated by the saved_audiences write capability.

Inputs: saved_audience_id (required), confirm.

audiences_get_customB

Get full detail of a single custom audience.

Inputs: custom_audience_id (required).

audiences_get_savedA

Get full detail of a single saved audience (including targeting JSON).

Inputs: saved_audience_id (required).

audiences_list_customB

List custom audiences for an ad account.

Inputs: account_id (required), limit.

audiences_list_savedB

List saved audiences for an ad account.

Inputs: account_id (required), limit.

audiences_update_savedA

Update a saved audience's name/description/targeting.

KNOWN LIMITATION: see audiences_create_saved — usually fails with code=3 unless the App has Advanced Access. Workaround: audiences_apply_to_adset().

Inputs: saved_audience_id (required), name, confirm, targeting, +1 more.

billing_get_account_spendA

Get total amount spent by an ad account.

If no date params: returns lifetime amount_spent on the account. If date params provided: aggregates spend from insights for that period.

Inputs: account_id (required), since, until, date_preset.

billing_get_funding_sourceA

Get info about the configured funding source on an ad account.

Returns metadata only — no sensitive details (card numbers, etc.).

Inputs: account_id (required).

billing_get_invoiceA

Get details of a single invoice.

Inputs: invoice_id (required).

billing_get_spend_limitsC

Get configured spending limits for an ad account.

Inputs: account_id (required).

billing_list_invoicesB

List invoices/billing documents for a Business Manager.

NOTE: This endpoint may require additional permissions or may be empty depending on the BM's payment configuration.

Inputs: business_id (required), limit.

billing_summary_all_accountsA

Get a spend summary across ALL accessible ad accounts for a date range.

Useful for dashboards / quick agency-wide visibility.

Inputs: date_preset.

catalog_createC

Create a new product catalog under a Business Manager.

Args: vertical: commerce, hotels, flights, destinations, home_listings, vehicles, etc.

Inputs: name (required), business_id (required), confirm, vertical.

catalog_create_productB

Create a single product in a catalog.

Args: retailer_id: Your internal SKU/identifier. price: Price in cents (e.g., 9990 = R$99.90). currency: ISO 4217 (BRL, USD, EUR, etc.). availability: in stock, out of stock, preorder, available for order, discontinued. condition: new, refurbished, used.

Inputs: url (required), name (required), brand (required), price (required), +9 more.

catalog_create_product_setA

Create a product set defined by a filter expression.

Example filter_spec: {"availability": {"i_contains": "in stock"}}

Inputs: name (required), catalog_id (required), filter_spec (required), confirm.

catalog_delete_productC

Delete a product from the catalog.

Inputs: product_id (required), confirm.

catalog_getB

Get details of a single catalog.

Inputs: catalog_id (required).

catalog_get_diagnosticsB

Get diagnostic issues affecting catalog quality and ad eligibility.

Inputs: catalog_id (required).

catalog_get_productC

Get details of a single product.

Inputs: product_id (required).

catalog_listB

List all product catalogs owned by a Business Manager.

Inputs: business_id (required), limit.

catalog_list_feedsA

List product feeds (CSV/XML uploads) attached to a catalog.

Inputs: catalog_id (required), limit.

catalog_list_productsC

List products in a catalog.

Inputs: catalog_id (required), limit.

catalog_list_product_setsB

List product sets within a catalog (used for Advantage+ Catalog campaigns).

Inputs: catalog_id (required), limit.

catalog_update_productC

Update arbitrary fields on a product.

Inputs: fields (required), product_id (required), confirm.

creatives_deleteA

Delete an ad creative. Will fail if any active ads still reference it.

Inputs: creative_id (required), confirm.

creatives_getB

Get full details of a single ad creative.

Inputs: creative_id (required).

creatives_listB

List all ad creatives in an account.

Inputs: account_id (required), limit.

creatives_list_imagesB

List images uploaded to an account's image library.

Inputs: account_id (required), limit.

creatives_list_videosB

List videos uploaded to an account's video library.

Inputs: account_id (required), limit.

creatives_previewB

Preview a creative in any ad format (returns rendered HTML).

Inputs: creative_id (required), ad_format.

creatives_upload_imageB

Upload an image to an ad account's image library. Returns the image hash.

Args: image_path: Absolute path to a local image file (PNG, JPG, GIF).

Inputs: account_id (required), image_path (required), confirm.

creatives_upload_videoA

Upload a video to an ad account's video library. Returns the video ID.

NOTE: Large videos (>1GB) require chunked upload — not supported here.

Inputs: account_id (required), video_path (required), name, confirm, +1 more.

graph_callA

Call any Graph API endpoint directly.

Args: method: GET, POST, or DELETE. endpoint: Path AFTER the version prefix, e.g. "act_123/campaigns" or "me/adaccounts". Do NOT include "https://graph.facebook.com/v21.0/" — that's added automatically. params: Query string parameters as a dict. data: Body parameters for POST as a dict. confirm: Required True for POST and DELETE.

Examples: - Read your user info: graph_call("GET", "me", {"fields": "id,name"}) - Get adaccount: graph_call("GET", "act_123", {"fields": "name,balance"}) - Pause ad: graph_call("POST", "12345", data={"status":"PAUSED"}, confirm=True)

Inputs: method (required), endpoint (required), data, params, +1 more.

graph_debug_tokenA

Inspect the currently loaded access token (scopes, expiry, app, type).

Useful for debugging permission issues without exposing the raw token.

Inputs: no named parameters.

graph_paginateA

Auto-paginate a GET endpoint and return a flat list of all data items.

Stops at max_pages to prevent runaway calls.

Inputs: endpoint (required), params, max_pages.

ig_delete_commentB

Permanently delete an IG comment.

Inputs: comment_id (required), confirm.

ig_get_accountC

Get profile info for an IG Business account.

Inputs: ig_account_id (required).

ig_get_account_insightsC

Get account-level insights for an IG Business account.

Common metrics: impressions, reach, profile_views, follower_count, website_clicks, email_contacts, phone_call_clicks, text_message_clicks, get_directions_clicks.

Inputs: metrics (required), ig_account_id (required), since, until, +1 more.

ig_get_insightsA

Get insights for an IG media post.

Common metrics: impressions, reach, engagement, saved, video_views, profile_visits, follows, likes, comments, shares, total_interactions. Different media types support different metrics.

Inputs: metrics (required), media_id (required).

ig_get_mediaB

Get details for a single IG media post.

Inputs: media_id (required).

ig_hide_commentC

Hide or unhide an IG comment.

Inputs: comment_id (required), hide, confirm.

ig_list_accountsA

List Instagram Business accounts via Pages the user has access to.

Inputs: no named parameters.

ig_list_commentsC

List comments on an IG media post.

Inputs: media_id (required), limit.

ig_list_mediaB

List recent posts (media) from an IG account.

Inputs: ig_account_id (required), limit.

ig_publish_imageA

Publish a single image to IG. Two-step: create container, then publish.

image_url must be publicly reachable (Meta will fetch it).

Inputs: image_url (required), ig_account_id (required), caption, confirm.

ig_publish_videoC

Publish a video/reel to IG. Two-step process.

Args: media_type: REELS (recommended) or VIDEO.

Inputs: video_url (required), ig_account_id (required), caption, confirm, +2 more.

ig_reply_to_commentC

Reply to an IG comment.

Inputs: message (required), comment_id (required), confirm.

insights_check_async_reportC

Check the status of an async insights report job.

Inputs: report_run_id (required).

insights_compare_periodsB

Compare insights between two date ranges side-by-side.

Args: period_a / period_b: {"since": "YYYY-MM-DD", "until": "YYYY-MM-DD"}

Inputs: level (required), period_a (required), period_b (required), entity_id (required), +1 more.

insights_fetch_async_reportC

Fetch results of a completed async insights report.

Inputs: report_run_id (required), limit.

insights_getA

Get insights for any entity (account, campaign, ad set, or ad).

Args: entity_id: act_... for an ad account, OR a campaign/adset/ad ID. level: account, campaign, adset, or ad. Determines aggregation. date_preset: today, yesterday, this_week_mon_today, last_7d, last_14d, last_28d, last_30d, last_90d, this_month, last_month, this_quarter, maximum, etc. Mutually exclusive with time_range. time_range: {"since": "YYYY-MM-DD", "until": "YYYY-MM-DD"}. fields: Comma-separated list. Defaults to a comprehensive standard set. breakdowns: e.g. ["age", "gender"], ["publisher_platform", "platform_position"], ["country"], ["device_platform"], ["impression_device"]. action_breakdowns: e.g. ["action_type", "action_destination"]. filtering: e.g. [{"field":"spend","operator":"GREATER_THAN","value":"10"}]. time_increment: 1, 7, monthly, all_days. Aggregates by time buckets. sort: e.g. ["spend_descending"], ["impressions_descending"].

Inputs: entity_id (required), sort, level, limit, +7 more.

insights_get_account_summaryB

Quick top-line spend/impressions/clicks for an entire account.

Inputs: account_id (required), date_preset.

insights_get_async_reportA

Submit an async insights job for large reports. Returns the report run ID.

Use insights_check_async_report(report_run_id) to check status, then insights_fetch_async_report(report_run_id) to get the data.

Inputs: account_id (required), level, fields, breakdowns, +2 more.

leads_get_formB

Get details of a single lead form including its question schema.

Inputs: form_id (required).

leads_get_leadB

Get full data for a single lead.

Inputs: lead_id (required).

leads_list_formsB

List lead generation forms attached to a Page.

Inputs: page_id (required), limit.

leads_list_from_adB

Retrieve all leads generated by a specific ad.

Inputs: ad_id (required), limit.

leads_list_from_formA

Retrieve actual leads submitted to a form.

Args: after_cursor: Pagination cursor from a previous response (paging.cursors.after).

Inputs: form_id (required), limit, after_cursor.

pages_delete_postB

Delete a post permanently.

Inputs: post_id (required), confirm.

pages_getC

Get detailed info about a Page.

Inputs: page_id (required).

pages_get_insightsC

Get Page insights metrics.

Args: metrics: e.g. ["page_impressions", "page_engaged_users", "page_post_engagements", "page_fans", "page_views_total"]. period: day, week, days_28, lifetime.

Inputs: metrics (required), page_id (required), since, until, +2 more.

pages_get_postA

Get details of a single post.

Inputs: post_id (required).

pages_hide_commentC

Hide or unhide a comment from a post.

Inputs: comment_id (required), hide, confirm.

pages_listB

List all Facebook Pages the System User can access.

Inputs: limit.

pages_list_commentsC

List comments on a post.

Inputs: post_id (required), limit.

pages_list_postsC

List published posts of a Page.

Inputs: page_id (required), limit.

pages_publish_photoB

Publish a photo to a Page from an external URL.

Inputs: page_id (required), image_url (required), caption, confirm, +1 more.

pages_publish_postB

Publish a text/link post to a Page.

Args: scheduled_publish_time: Unix timestamp; if set, post is scheduled. Set published=False.

Inputs: message (required), page_id (required), link, confirm, +2 more.

pages_reply_to_commentC

Reply to a comment.

Inputs: message (required), comment_id (required), confirm.

pixels_createB

Create a new pixel under an ad account.

Inputs: name (required), account_id (required), confirm.

pixels_getB

Get detailed info about a single pixel including diagnostics.

Inputs: pixel_id (required).

pixels_get_assigned_usersB

List users/system users assigned to this pixel.

Inputs: pixel_id (required).

pixels_get_statsB

Get fired-event statistics for a pixel.

Args: aggregation: event, browser_type, custom_data_field, device_os, device_type, host, pixel_fire, url.

Inputs: pixel_id (required), aggregation.

pixels_listB

List all pixels (datasets) owned by an ad account.

Inputs: account_id (required), limit.

pixels_send_capi_eventA

Send a server-side conversion event via Conversions API.

PII fields (email, phone, names, external_id) are auto-hashed with SHA-256 before being sent — Meta requires this.

Args: event_name: Standard event (Purchase, Lead, AddToCart, ViewContent, etc.) or a custom event name. action_source: website, app, phone_call, chat, email, physical_store, system_generated, business_messaging. custom_data: e.g. {"value": 99.90, "currency": "BRL", "content_ids": ["sku123"]}. event_id: Use to deduplicate against client-side pixel events. test_event_code: For Test Events tab in Events Manager (use during dev).

Inputs: pixel_id (required), event_name (required), fbc, fbp, +14 more.

pixels_share_to_businessB

Share a pixel with another Business Manager.

Inputs: pixel_id (required), business_id (required), confirm.

wa_create_templateB

Submit a new message template for review.

Args: category: AUTHENTICATION, MARKETING, or UTILITY. components: List of header/body/footer/buttons components.

Inputs: name (required), waba_id (required), category (required), language (required), +2 more.

wa_delete_templateC

Delete a template by name.

Inputs: waba_id (required), template_name (required), confirm.

wa_get_phone_numberB

Get details for a specific WhatsApp phone number.

Inputs: phone_number_id (required).

wa_list_business_accountsA

List WhatsApp Business Accounts (WABAs) accessible to the user.

Inputs: no named parameters.

wa_list_phone_numbersA

List phone numbers attached to a WhatsApp Business Account.

Inputs: waba_id (required).

wa_list_templatesA

List approved/pending message templates for a WABA.

Inputs: waba_id (required), limit.

wa_send_image_messageB

Send an image (from a public URL) via WhatsApp.

Inputs: to (required), image_url (required), phone_number_id (required), caption, +1 more.

wa_send_template_messageA

Send a pre-approved template message (works any time, no 24h window).

Args: components: e.g. [{"type":"body","parameters":[{"type":"text","text":"João"}]}].

Inputs: to (required), template_name (required), phone_number_id (required), confirm, +2 more.

wa_send_text_messageA

Send a free-form text message to a WhatsApp number.

NOTE: Free-form messages only work within the 24h customer service window (after the user messaged you). Outside that window, use a template.

Args: to: Recipient phone number with country code, no + (e.g., "5511999998888").

Inputs: to (required), text (required), phone_number_id (required), confirm, +1 more.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    C2.9/5.0

    Scored across 118 tools

    Disambiguation3/5

    The domain prefixes help, but several tools have fuzzy boundaries: insights_get overlaps with insights_get_account_summary and billing spend tools, ads_get_ad_preview overlaps with creatives_preview, and catalog_list is easy to mistake for catalog_list_products. Descriptions usually clarify the intended entity, but the agent has to rely on reading closely rather than clear separation.

    Naming Consistency4/5

    Most tools follow a consistent domain_prefix + verb_noun pattern (ads_list_campaigns, ads_create_campaign, ads_pause_adset, wa_send_text_message). There are a few deviations like billing_summary_all_accounts (missing verb), bare catalog_list/creatives_list, and the generic insights_get, but these are minor against the overall predictable structure.

    Tool Count1/5

    118 tools is far beyond the extreme threshold and makes the server unwieldy for an agent to navigate. The broad coverage across Ads, Catalogs, Creatives, IG, Pages, WhatsApp, Leads, Pixels, Billing, and Graph API would be much better scoped as several smaller, focused servers.

    Completeness3/5

    The core ads lifecycle—campaigns, ad sets, ads, creatives, insights—is well covered. However, several included subdomains are incomplete: catalogs and product sets can be created but not updated/deleted, custom audiences are read-only, and creatives have no update path, which are notable gaps that could block agents working outside the main campaign path.

    Maintenance

    ActivityNo data
    ResponsivenessOutdated data

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      Not graded
      quality
      D
      maintenance
      Unified MCP server for managing Meta Ads, LinkedIn Ads, Google Ads, GA4, and Search Console with 89 read/write tools, multi-account support, OAuth setup, and safe dry-run mutations.
      MIT
    • A
      license
      Not graded
      quality
      D
      maintenance
      MCP server for Meta/Facebook Marketing API allowing you to view and manage ad accounts, campaigns, ad sets, ads, and creatives, as well as fetch insights and upload ad images.
      MIT
    • A
      license
      B
      quality
      D
      maintenance
      MCP server for Meta Ads providing 30 tools for account discovery, campaign management, targeting research, and insights. Designed with LLM-friendly outputs and productivity features like cloning and bulk operations.
      31
      1
      MIT
    • F
      license
      Not graded
      quality
      B
      maintenance
      MCP server for the Meta Marketing API enabling ad performance reports and operational adjustments like pause/resume and budget changes on client ad accounts, with strict allowlist and read-only safeguards.
      -