使用说明
Meta Ads MCP Server
An optimization-first MCP server that bridges LLMs with the Meta Marketing API — tools for querying, managing, and optimizing your ad accounts through natural language.
Ask Claude or Gemini to "show me which creatives are fatiguing" or "give me an optimization snapshot for this account" — and it just works.
This is not an officially supported Meta product.
✨ Features
- 📊 99 Tools — Discovery, reporting, activity history, diagnostics, social feedback, targeting, research, auth helpers, and controlled writes
- 🔍 Optimization-First — Not just CRUD: pacing, fatigue, audience, and snapshot diagnostics built in
- 📖 Built-in Docs — Object model, metrics, optimization playbook, current v26 notes, and historical v25 notes available as tools and MCP resources
- 🎯 Full Targeting Suite — Interest, behavior, demographic, and geo search with audience size estimation
- 🔑 Auth Helpers — Generate tokens, exchange codes, refresh tokens, and validate scopes
- 🖼️ Creative Ops — Preview ads, upload assets, and set up A/B tests
- 🔎 Tool Search — FastMCP 3.4.7 tool search lets the LLM discover tools on demand instead of loading the full catalog up front
- 🖥️ Works Everywhere — Claude Code, Claude Desktop, Gemini CLI, or any MCP client
📋 Available Tools (99)
🔍 Discovery
| Tool | Description |
|---|---|
list_ad_accounts |
List all accessible ad accounts |
get_ad_account |
Get details for a specific ad account |
get_account_pages |
Get Pages linked to an ad account |
list_instagram_accounts |
List Instagram accounts linked to an ad account |
list_campaigns |
List campaigns in an ad account, optionally filtered by name |
get_campaign |
Get details for a specific campaign |
list_adsets |
List ad sets in a campaign or account, optionally filtered by name |
get_adset |
Get details for a specific ad set |
list_ads |
List ads in an ad set or account, optionally filtered by name |
get_ad |
Get details for a specific ad |
list_audiences |
List custom and lookalike audiences with lightweight summary fields by default |
get_audience |
Read one custom or lookalike audience by ID |
list_creatives |
List ad creatives in an account |
get_creative |
Read one ad creative by ID, including url_tags and full creative-spec fields |
📊 Analysis
| Tool | Description |
|---|---|
get_insights |
Backward-compatible alias for older insights calls |
get_entity_insights |
Get performance insights with optional scalar action columns |
get_performance_breakdown |
Break down performance by dimension |
summarize_actions |
Count selected actions without returning full action arrays |
compare_time_ranges |
Compare metrics across two time ranges |
compare_performance |
Compare metrics across multiple entities |
export_insights |
Export insights as CSV or structured data |
create_async_insights_report |
Create a large async report with lean defaults or an explicit full preset |
create_async_insights_report_batch |
Submit up to 10 independent breakdown reports sequentially |
get_async_insights_report |
Poll or bounded-wait for an async report and retrieve compact rows |
⚡ Optimization
| Tool | Description |
|---|---|
get_account_optimization_snapshot |
Full account health and optimization summary |
get_campaign_optimization_snapshot |
Campaign-level optimization summary |
get_native_optimization_signals |
v26-native campaign, ad-set, or ad delivery and issue signals |
get_account_health_snapshot |
Account totals with previous-window and YoY comparisons |
detect_auction_overlap |
Directional cannibalization screen across campaigns |
get_ad_feedback_signals |
Feedback availability and quality-ranking signals |
get_budget_pacing_report |
Budget pacing and spend trajectory |
get_creative_performance_report |
Creative performance analysis |
get_creative_fatigue_report |
Detect creative fatigue signals |
get_audience_performance_report |
Audience segment performance analysis |
get_delivery_risk_report |
Delivery risks and issues |
get_learning_phase_report |
Learning phase status for ad sets |
get_recommendations |
Broad Meta-native opportunity scan |
get_budget_opportunities |
Budget and scaling opportunities |
get_creative_opportunities |
Creative asset and format opportunities |
get_audience_opportunities |
Audience and targeting opportunities |
get_delivery_opportunities |
Delivery and reach opportunities |
get_bidding_opportunities |
Bid strategy opportunities |
💬 Social Feedback
| Tool | Description |
|---|---|
get_ad_social_context |
Resolve Facebook post and Instagram media ids behind an ad |
list_ad_comments |
Read compact raw Facebook or Instagram comments for an ad or social object |
list_comment_replies |
Continue paginated replies for one Facebook or Instagram comment |
list_page_recommendations |
Read compact Facebook Page recommendations, reviews, or testimonials |
🧾 Activity History
| Tool | Description |
|---|---|
list_change_history |
Read account, campaign, ad set, or ad activity; object-scoped calls derive the owning account when account_id is omitted |
🎯 Planning & Targeting
| Tool | Description |
|---|---|
search_interests |
Search interest targeting options |
get_interest_suggestions |
Get related interest suggestions |
validate_interests |
Validate interest targeting IDs |
search_geo_locations |
Search geographic targeting options |
get_targeting_categories |
Get available targeting categories |
search_behaviors |
Search behavior targeting options |
search_demographics |
Search demographic targeting options |
estimate_audience_size |
Estimate audience size for a targeting spec |
get_reach_frequency_predictions |
Get reach and frequency predictions |
✏️ Writes
| Tool | Description |
|---|---|
create_campaign |
Create a new campaign |
update_campaign |
Update campaign settings |
delete_campaign |
Delete a campaign |
create_ad_set |
Create or validate an ad set, including bid strategy and constraints |
create_ad |
Create or validate an ad using a top-level or nested creative ID |
delete_adset |
Explicitly delete an ad set |
delete_ad |
Explicitly delete an ad |
set_campaign_status |
Pause or enable a campaign |
set_adset_status |
Pause or enable an ad set |
set_ad_status |
Pause or enable an ad |
update_campaign_budget |
Update a campaign's budget |
update_adset_budget |
Update an ad set's budget |
update_adset_bid_amount |
Update an ad set's bid amount |
update_campaign_bid_strategy |
Update a campaign's bidding strategy |
update_adset_bid_strategy |
Update an ad set's bidding strategy and constraints |
create_custom_audience |
Create a custom audience |
create_lookalike_audience |
Create a lookalike audience |
update_custom_audience |
Update a custom audience |
delete_audience |
Delete an audience |
create_ad_creative |
Create a new ad creative |
update_creative |
Update an ad creative |
delete_creative |
Delete an ad creative |
🖼️ Creative Ops
| Tool | Description |
|---|---|
get_ad_image |
Get ad image details |
preview_ad |
Preview an ad in various formats |
upload_creative_asset |
Upload an image or video asset |
setup_ab_test |
Set up an A/B test |
🔬 Public Research
| Tool | Description |
|---|---|
search_ads_archive |
Search the Meta Ad Library for competitor creatives |
🔑 Auth Helpers
| Tool | Description |
|---|---|
generate_auth_url |
Generate an OAuth authorization URL |
exchange_code_for_token |
Exchange an auth code for an access token |
refresh_to_long_lived_token |
Refresh a short-lived token to a long-lived one |
generate_system_user_token |
Generate a system user access token |
get_token_info |
Get info about the current token |
validate_token |
Validate token scopes and expiry |
📖 Docs & Utility
| Tool | Description |
|---|---|
get_meta_object_model |
Meta Ads object model reference |
get_metrics_reference |
Insights metrics reference |
get_v26_notes |
Current Marketing API v26.0 capabilities and migration notes |
get_v25_notes |
Historical Marketing API v25.0 release notes |
get_optimization_playbook |
Optimization best practices playbook |
health_check |
Server health check |
get_capabilities |
List server capabilities |
list_mutation_tools |
List write tools and safety notes |
read_overflow_artifact |
Retrieve one bounded JSON chunk from an oversized response |
delete_overflow_artifact |
Delete an oversized response artifact after retrieval |
FastMCP 3.4.7 also exposes dynamic search tools at runtime:
| Tool | Description |
|---|---|
search_tools |
Search for tools by keyword |
call_tool |
Call a discovered tool by name |
These let the LLM discover the right tool on demand instead of loading the full catalog into context up front.
call_tool accepts name or the common tool_name alias, and arguments may
be a JSON object or an object-valued JSON string. Familiar list_ad_sets,
get_ad_set, and delete_ad_set spellings resolve to the canonical adset
tool names without duplicating search results. Public string-list parameters,
including fields, breakdowns, statuses, countries, scopes, and action types,
accept either JSON string lists or comma-separated strings.
Budget and bid inputs use human-readable account-currency units. The server
resolves currency from the owning Ad Account, converts values to Meta minor
units exactly, and rejects precision the currency cannot represent (for
example, fractional JPY). Campaign bidding strategy and Ad Set bid amounts are
separate operations because Meta does not expose a campaign-level bid_amount.
📚 MCP Resources
| Resource | Description |
|---|---|
meta://docs/object-model |
Meta Ads object hierarchy |
meta://docs/insights-metrics |
Available insights metrics |
meta://docs/v25-notes |
v25.0 API release notes |
meta://docs/v26-notes |
Current v26.0 API capabilities and migration notes |
meta://docs/optimization-playbook |
Optimization best practices |
meta://docs/tool-routing |
Tool routing guide |
🚀 Quick Start
Prerequisites
- Python 3.12+
uv(recommended) orpipx- A Meta access token with the scopes needed for the tools you want to use
- Social feedback tools also need Page or Instagram permissions, such as
pages_read_user_contentfor Page comments/recommendations and the relevant Instagram comment permissions for IG media comments
1. Get Your Credentials
Set your access token:
export META_ACCESS_TOKEN='YOUR_META_ACCESS_TOKEN'
Optional settings:
export META_DEFAULT_ACCOUNT_ID='act_1234567890'
export META_API_VERSION='v26.0'
export META_APP_ID='YOUR_APP_ID'
export META_APP_SECRET='YOUR_APP_SECRET'
export META_REDIRECT_URI='https://example.com/callback'
export META_EXPORT_DIR='/path/to/meta-ads-exports'
export META_EXPORT_TTL_SECONDS='86400'
export META_EXPORT_MAX_FILES='100'
export META_EXPORT_MAX_BYTES='1000000000'
You can also place these in a .env file at the repo root.
When META_APP_SECRET is set, authenticated Graph API requests using the
configured META_ACCESS_TOKEN automatically include Meta's HMAC-SHA256
appsecret_proof. A different token supplied as a request override is left
unproved because it may belong to another app. The secret and generated proof
are never included in public error payloads.
Marketing API v26 audit
The repository includes an SDK-backed compatibility audit that compares local default field projections with Meta's generated Python Business SDK schemas:
uv run audit-meta-sdk-schema \
--sdk-repo /path/to/facebook-python-business-sdk \
--base-ref 25.0.3 \
--target-ref 26.0.2
The default API is v26.0; the audit target is SDK 26.0.2. These are
different version numbers. See the v26 compatibility audit
for schema findings and the read-only live validation gate.
With META_API_VERSION=v26.0 or newer, use
get_native_optimization_signals for entity-native delivery, learning, issue,
automation, and recommendation payloads. get_entity_insights can opt into
the v26 instagram_profile_follow metric with
include_instagram_profile_follow=true. Both options fail early if an explicit
environment override selects v25. Restart the MCP to pick up a version change.
get_recommendations and the five typed opportunity tools accept Meta-native
recommendation_names and recommendation_stages filters, as lists or CSV.
For example, recommendation_stages=["PCR"] limits Meta's results before any
local category filtering. Omitting the filters keeps the existing broad scan.
Stage codes (MFR, PCR, PFR) are passed through without invented meanings.
Providing either filter requires API v26 or newer and fails before a request
under an explicit v25 override; unfiltered recommendation scans remain available.
New SDK fields and breakdowns use existing generic inputs: for example,
get_entity_insights(..., fields=["shop_clicks"]) or
get_entity_insights(..., breakdowns=["placement_path"]). Campaign reads now
also accept custom fields. These examples show input paths, not guaranteed
account eligibility or valid combinations. The live v26 account rejects the
SDK-listed campaign bid_constraints field, so it is not a default projection.
The ad persona field also requires permissions unavailable to the tested token.
Use get_v26_notes for the full relevant additions and compatibility caveats.
Overflow settings are optional. Oversized MCP responses are saved as complete,
owner-only JSON artifacts and replaced inline by an opaque export_id. Use the
visible call_tool proxy with name="read_overflow_artifact" and the returned
export_id, repeating with next_offset to retrieve the complete response
from local or remote MCP transports. Then use call_tool with
name="delete_overflow_artifact". When META_EXPORT_DIR is omitted, the
server uses the operating system's temporary meta-ads-mcp-exports directory.
The 64 KB threshold applies only to the inline MCP response: Meta's complete
result is retained in the artifact and is not truncated or discarded.
Archive schema v2 uses compact JSON and stores identical JSON compatibility
text only once, under tool_result.structured_content. Distinct or annotated
text remains intact. Overflow notices include archive size and retention.
Artifacts expire after 24 hours by default, and the store retains at most 100
response artifacts totaling 1 GB of JSON payload data; small private integrity
manifests are stored alongside those payloads. The three retention variables
above override those defaults.
If the host lacks secure directory-relative file operations and process locks,
archival fails closed and the tool returns guidance to narrow the request.
Synchronous get_entity_insights (and its get_insights alias) omits duplicate
raw action arrays by default, keeping numeric action maps, derived metrics, and
requested flatten_actions columns. Custom action counts and values remain
available without an extra opt-in. Set include_raw_actions=true to retain raw
arrays, including named attribution-window values needed for click/view
comparisons; generic maps are not window-specific totals. summarize_actions
and JSON/CSV exports retain their existing action output. Existing callers that
read raw action arrays must now opt in. Dimensional action_breakdowns (anything
beyond action_type, such as action_device) require include_raw_actions=true;
read the raw records for those dimensions, not the scalar maps or derived totals.
Compact calls fail before making a request instead of discarding the dimensions.
For placement reporting across copies of an ad, use level="ad" with the
parent object_id="act_ACCOUNT_ID" (or a campaign/ad set ID),
breakdowns=["publisher_platform","platform_position"], and native
filtering=[{"field":"ad.name","operator":"CONTAIN","value":"NAME_PREFIX"}].
level already selects row granularity; no separate row_level is needed.
Filters also work with exports and async report creation. Synchronous summaries
cover the returned page only: check summary.complete and continue with
paging.after before treating the result as exhaustive.
Alternatively, set fetch_all=true to collect up to 1,000 rows from the first
page onward in one call (also supported by get_insights and export_insights).
The server combines metrics after pagination, or fails explicitly if the scan
cannot finish. Narrow the query or use async reports for larger results.
For an account-wide fatigue sweep, call get_creative_fatigue_report with
account_id or level="account", object_id="act_ACCOUNT_ID". It reads both
windows at ad granularity, follows pagination, and includes ad/campaign/ad set
names. Findings use a transparent ordering: minimum impressions across both
windows, CTR decline, frequency increase, current spend, then ad ID. This is a
rule-based priority order, not statistical significance. Each window
defaults to a 1,000-ad bound; use max_ads=5000 for a larger account (maximum
10,000). Scans beyond the selected bound or unusable pagination fail explicitly;
no partial diagnosis is returned. Flagged ads receive current creative IDs via
up to 100 extra metadata reads (max_creative_lookups, 0–1,000; zero disables
enrichment). Missing IDs, failed reads, rate limits, and the lookup cap remain
explicit without discarding findings. An exhausted transient error stops further
optional reads for that call; unread identities are marked skipped_transient_error.
Exact creative-ID groups cover flagged ads only; current IDs do not prove
historical creative identity and are not
inferred from names. creative_identity_complete is separate from the complete
Insights scan. Confidence remains null/uncalibrated. Medium severity requires
at least 20% CTR decline and 20% frequency rise; high requires at least 50% of
both. These thresholds are a prioritization policy, not probabilities.
Use list_ads(name_contains_any=["Ada","Grace"], whole_term_match=true, effective_status=["ACTIVE"], fields=["id","name"]) to search several names in
one bounded inventory scan. Matching is case-insensitive; whole-term mode treats
punctuation and underscores as separators and avoids matching Ada in Adam.
Each ad includes matched_terms. The scan follows pagination up to 1,000 ads and
fails explicitly if it cannot finish; completeness still reflects the selected
scope and status filters, not all historical ads.
get_ad_image joins asset labels to configured_image_rules, retaining crop
coordinates, explicit default flags, rule constraints, and unresolved labels.
It includes reporting-position hints (Instagram stream becomes feed;
unknown values pass through). These are configured eligibility rules, not proof
that a specific image served. Placement Insights show delivery totals, but do
not establish image-level delivery when several assets are eligible.
Pass ad_ids=["AD_1","AD_2"] instead of ad_id for a collection of up to 100
distinct ads. Creative reads and image-hash lookups are reused within that call,
with image caching scoped to the owning account. Unresolved image hashes remain
explicit. This uses the existing per-object Graph reads, not a new batch job or
persistent cache.
For an analysis-only server, set META_READ_ONLY=true in its launch environment
and restart it. This opt-in mode hides mutation tools from search and rejects
advertising mutations, asset uploads, A/B-test setup, and token mutations before
execution—even through call_tool or compatibility aliases. Unknown tools fail
closed. Reporting jobs and local overflow-artifact cleanup remain available.
Use an ads_read token as an additional permission boundary where possible.
Analysis agents using tool search need access to search_tools, call_tool, and
get_capabilities; allowing only underlying tool names is not sufficient.
The default remains writable; no existing server or dbt agent configuration is
changed automatically.
For click/view attribution comparisons, use get_entity_insights with explicit
dates, fields=["actions","action_values"],
use_unified_attribution_setting=false,
action_attribution_windows=["7d_click","1d_view"], and
include_raw_actions=true. Read the named window keys from the raw purchase
action entries. Meta's generic value can differ from them even for a single
requested window; core metrics, action maps, and flatten_actions use that
generic value, not window-specific totals. Do not assume the window values or
overlapping action aliases are additive. Historical ad set setting changes
still require retained activity or warehouse history.
Async insights use a lean scalar field set by default; pass
field_preset="full" or explicit fields when the wider Meta response is
required. Pass flatten_actions=["purchase","purchase_value"] when creating
and retrieving a report to request only the required action dependencies and
promote those values into scalar row columns. Async retrieval omits verbose
action arrays and maps unless include_raw_actions=true, while preserving
requested scalar fields such as spend, impressions, and clicks. Use wait=true
for a bounded server-side poll, or inspect the returned job.ready,
job.terminal, and job.poll_after_seconds fields yourself.
Graph API failures are returned as allowlisted structured error JSON with Meta
codes, subcodes, user guidance, retry timing, and usage diagnostics when
available. Reads may retry transient failures; create, update, and delete calls
are not automatically retried after ambiguous failures. If
mutation_outcome_unknown=true, retryable is false and callers must verify
current object state before deciding whether to retry.
META_DEFAULT_ACCOUNT_ID is an optional convenience, not a requirement for
object-scoped activity history. When a campaign, ad set, or ad ID is supplied,
the server looks up its owning account and validates any explicit or default
account against that ownership.
Don't have a token? Use the built-in auth helper tools (generate_auth_url → exchange_code_for_token → refresh_to_long_lived_token) to create one interactively.
2. Install & Run
Option A: Claude Code (recommended)
# Install from repo
claude mcp add --transport stdio MetaAds \
--env META_ACCESS_TOKEN=YOUR_META_ACCESS_TOKEN \
--env META_DEFAULT_ACCOUNT_ID=act_1234567890 \
-- pipx run --spec git+https://github.com/jeffreywlai/meta-ads-mcp.git run-meta-ads-mcp
Or from a local clone:
claude mcp add --transport stdio MetaAds \
--env META_ACCESS_TOKEN=YOUR_META_ACCESS_TOKEN \
--env META_DEFAULT_ACCOUNT_ID=act_1234567890 \
-- uv run --directory /path/to/meta-ads-mcp -m meta_ads_mcp.stdio
Type /mcp in Claude Code to verify the server is connected.
Option B: Gemini CLI
Add to your Gemini MCP configuration:
{
"mcpServers": {
"MetaAds": {
"command": "pipx",
"args": [
"run", "--spec",
"git+https://github.com/jeffreywlai/meta-ads-mcp.git",
"run-meta-ads-mcp"
],
"env": {
"META_ACCESS_TOKEN": "YOUR_META_ACCESS_TOKEN",
"META_DEFAULT_ACCOUNT_ID": "act_1234567890"
},
"timeout": 30000
}
}
}
Option C: Direct Launch
uv sync
uv run -m meta_ads_mcp.stdio
For HTTP mode:
uv run -m meta_ads_mcp.server
💬 Usage Examples
Once connected, just talk naturally:
Discovery
"List my accessible ad accounts"
"Show me the campaigns in act_1234567890"
"What Instagram accounts are linked to this ad account?"
"Find the Pages I can use for creative setup"
Reporting & Analysis
"Show me account-level insights for the last 7 days"
"Compare these two campaigns on ROAS, CPA, and CTR"
"Break down campaign performance by country"
"Export ad-level insights for the last 30 days as CSV"
"Create async reports for country and device breakdowns, then wait for each result"
"Return purchase and purchase_value as flat columns without raw action arrays"
Optimization
"Give me an optimization snapshot for this account"
"Which creatives are fatiguing in this campaign?"
"What are the biggest delivery risks right now?"
"Which audience segments are underperforming?"
Planning & Research
"Estimate audience size for this targeting spec"
"Find interest suggestions related to running and fitness"
"Search demographic targeting categories for new parents"
"Search the Meta ads archive for competitor creatives"
Writes
"Create a paused campaign for this account"
"Pause this campaign"
"Update this ad set budget to 75 dollars daily"
"Validate a target-ROAS ad set with a 30000 ROAS floor without creating it"
"Create an ad in this ad set using creative 123"
🏗️ Project Structure
meta-ads-mcp/
├── meta_ads_mcp/
│ ├── coordinator.py # Shared FastMCP instance & tool search config
│ ├── stdio.py # Server entry point (Claude Code / stdio)
│ ├── server.py # Server entry point (Gemini / SSE)
│ ├── config.py # Configuration and env loading
│ ├── auth.py # Auth utilities
│ ├── diagnostics.py # Derived metrics & optimization heuristics
│ ├── errors.py # Error handling
│ ├── graph_api.py # Meta Graph API client
│ ├── money.py # Account-currency resolution & exact conversion
│ ├── normalize.py # Response normalization
│ ├── pagination.py # Graph API pagination helpers
│ ├── schemas.py # Pydantic schemas
│ ├── tools/
│ │ ├── ads.py # Ad management
│ │ ├── activity.py # Activity and change-history reads
│ │ ├── audiences.py # Audience management
│ │ ├── auth_tools.py # Auth helper tools
│ │ ├── campaigns.py # Campaign & ad set management
│ │ ├── creatives.py # Creative management
│ │ ├── diagnostics.py # Optimization & diagnostics
│ │ ├── discovery.py # Account & entity discovery
│ │ ├── docs.py # Built-in documentation tools
│ │ ├── execution.py # Write operations
│ │ ├── insights.py # Reporting & analysis
│ │ ├── recommendations.py # AI recommendations
│ │ ├── research.py # Ad Library research
│ │ ├── social_feedback.py # Comments & Page recommendations
│ │ ├── targeting.py # Targeting & planning
│ │ └── utility.py # Health check & capabilities
│ └── context/ # Embedded docs & playbooks
├── tests/ # Mirrors source structure
├── CLAUDE.md
├── GEMINI.md
├── SPEC.md
├── pyproject.toml
└── README.md
🛠️ Development
uv sync --extra dev # Install runtime and test dependencies
uv run --extra dev pytest # Run tests
Project Notes
- Keep Graph API calls isolated in
meta_ads_mcp/graph_api.py - Prefer optimization and diagnostics tools before mutations
- Return dicts and lists rather than serialized JSON strings
- Treat all account IDs and object IDs as strings
- Keep the implementation aligned with Marketing API v26.0
📄 License
Licensed under the MIT License.
📬 Contact
Questions, suggestions, or feedback? Open an issue.
Built with FastMCP 3.4.7 and Meta Marketing API v26.0