Meta Ads MCP
Allows interacting with Facebook Pages, posts, insights, and comment moderation, as well as Facebook ads through the Meta Marketing API.
Provides tools for Instagram media management, publishing, insights, and comment moderation.
Provides tools for managing Meta ad accounts, campaigns, ad sets, ads, insights, billing, audiences, catalogs, leads, and more via the Meta Marketing API.
Provides tools for WhatsApp Business account management, phone numbers, message templates, and sending messages.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@Meta Ads MCP List my Meta ad accounts."
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
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.
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 | OS credential store first (Windows Credential Manager / macOS Keychain via |
Destructive calls | The model calls |
|
Errors | Meta's opaque | 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.ps1The 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 promptedThen register it with your client:
claude mcp add meta-ads --scope user -- /path/to/.venv/bin/python /path/to/src/server.pyRestart 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 |
| 29 | Campaigns, ad sets, ads — create, update, duplicate, pause, activate, delete, schedules, previews |
| 12 | Product catalogs, feeds, product sets, diagnostics |
| 12 | Media, insights, publishing (image/video), comment moderation |
| 11 | Posts, insights, publishing, comment moderation |
| 10 | Link & carousel creatives, image/video upload, previews |
| 9 | Business accounts, phone numbers, message templates, sending |
| 8 | Custom & saved audiences, applying them to ad sets |
| 7 | Pixel management, stats, sharing, Conversions API events |
| 6 | Reports, period comparison, async report lifecycle |
| 6 | Spend, limits, funding source, invoices, cross-account summary |
| 5 | Lead forms and lead retrieval |
| 3 |
|
graph_callmatters 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_— pass123456oract_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 |
Docker image | Secrets baked into build layers |
|
Public endpoint | Brute force, DNS rebinding | Bearer auth with constant-time compare, global rate limit, |
Destructive calls | A campaign paused or deleted by mistake |
|
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 |
| — | Token, if you're not using the credential store |
| — | Fallback Business Manager ID |
|
| Graph API version |
|
| Read timeout, seconds — Meta's validation paths can sit 20–40s |
|
| Local rate limiter |
|
|
|
| — | Host allowlist — required for public HTTP deploys |
|
| 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 deployfly.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 toolsContributing
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 toolsads_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_create_carousel_adB
Create a multi-card carousel creative.
Args: cards: List of {"link": ..., "image_hash": ..., "name": ..., "description": ...}.
Inputs: link (required), name (required), cards (required), message (required), +5 more.
creatives_create_link_adA
Create a single-image or single-video link ad creative.
Either image_hash OR video_id must be provided. Use creatives_upload_image / creatives_upload_video to get those values first.
Inputs: link (required), name (required), message (required), page_id (required), +8 more.
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.
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
Scored across 118 tools
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.
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.
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.
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
Related MCP Connectors
Meta Ads MCP server: run Facebook and Instagram campaigns, ad sets, ads and reports from AI.
Meta Ads MCP server: 10 tools for Facebook and Instagram reports, ads, Ad Library. Ads start paused.
Hosted Meta Ads MCP server for Claude, ChatGPT and Cursor, with each action switched per account.
Meta Ads MCP server with 47 tools for campaigns, creatives, audiences, and insights.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceUnified 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
- AlicenseNot gradedqualityDmaintenanceMCP 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
- AlicenseBqualityDmaintenanceMCP 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.311MIT
- FlicenseNot gradedqualityBmaintenanceMCP 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.-