@0xfabrica/mcp-meta-ads
v1.0.1
Published
Open-source MCP server for Meta Ads management — read insights, create campaigns, update budgets, and pause ads via any MCP-compatible AI client.
Maintainers
Readme
mcp-meta-ads
Open-source MCP server for safe Meta Ads management from AI coding agents and any MCP-compatible client.
Features
- 7 MCP tools for reading metrics, managing budgets, pausing entities, and creating campaigns.
- Built-in
doctorcommand for offline account health checks. - Safety first — every created object is forced to
PAUSED. Credentials never leak to LLM output. - Works with any MCP client — Claude Code, Cursor, Windsurf, Cline, or your own agent.
Installation
Option A: npm (recommended)
npm install -g @0xfabrica/mcp-meta-adsOr run directly with npx:
npx @0xfabrica/mcp-meta-adsOption B: From source
git clone https://github.com/0xfabrica/mcp-meta-ads.git
cd mcp-meta-ads
cp .env.example .env # fill in your Meta credentials
npm install
npm run buildSetup with MCP clients
Claude Code
claude mcp add --transport stdio mcp-meta-ads -- npx @0xfabrica/mcp-meta-adsClaude Desktop
Add to your claude_desktop_config.json:
{
"mcpServers": {
"mcp-meta-ads": {
"command": "npx",
"args": ["@0xfabrica/mcp-meta-ads"],
"env": {
"META_APP_ID": "your_app_id",
"META_APP_SECRET": "your_app_secret",
"META_ACCESS_TOKEN": "your_access_token",
"META_AD_ACCOUNT_ID": "your_ad_account_id"
}
}
}
}Cursor / Windsurf / Cline
{
"name": "mcp-meta-ads",
"transport": "stdio",
"command": "npx",
"args": ["@0xfabrica/mcp-meta-ads"]
}Environment variables
Create a .env file in the project root (see .env.example):
| Variable | Description |
|----------|-------------|
| META_APP_ID | Your Meta app ID |
| META_APP_SECRET | Your Meta app secret |
| META_ACCESS_TOKEN | Long-lived access token with ads_read and ads_management permissions |
| META_AD_ACCOUNT_ID | Numeric ad account ID (with or without act_ prefix) |
| META_API_VERSION | Graph API version (default: v25.0) |
API compatibility notes
Meta's current Marketing API requires a few fields that older integrations could omit. This server now sends them by default when creating campaigns and ad sets:
is_adset_budget_sharing_enabled=falseon campaign creation when not using campaign budget sharingbid_strategy=LOWEST_COST_WITHOUT_CAPon ad set creationtargeting.targeting_automation.advantage_audience=0on ad set targeting
For OUTCOME_SALES, the server also switches the ad set to sales-compatible delivery settings:
optimization_goal=OFFSITE_CONVERSIONSpromoted_object.custom_event_type=PURCHASEwhenpixel_idis provided
For link creatives, the server uses object_story_spec.link_data.picture instead of deprecated/unsupported fields such as image_url or link_caption.
If instagram_actor_id is provided, it is sent as instagram_user_id in the creative payload. Your ad account must have access to that Instagram account or Meta will reject the creative with a permissions error.
MCP tools
| Tool | Description | Writes? |
|------|-------------|---------|
| get_account_insights | Account-level spend, CPA, CPC, CTR, ROAS for a date range | No |
| list_active_campaigns | Active campaigns with ad set metrics (last 7 days) | No |
| get_campaign_performance | Deep drill into a single campaign with ad set + ad metrics | No |
| get_entity_insights | Inspect any campaign, ad set, or ad by ID | No |
| pause_underperforming_entity | Pause a campaign, ad set, or ad | Yes |
| update_budget | Update daily budget on a campaign or ad set | Yes |
| create_campaign | Create a full campaign > ad set > ad structure (always PAUSED) | Yes |
get_account_insights
since: "2025-01-01" # YYYY-MM-DD
until: "2025-01-31" # YYYY-MM-DDReturns spend, CPA, CPC, CTR, ROAS, impressions, clicks, and raw action arrays.
list_active_campaigns
limit: 25 # optional, max 100Returns active campaigns with their ad sets and last-7-days metrics.
get_campaign_performance
campaign_id: "123456789"
days: 7 # optional, max 90
include_ads: true # optional
ad_limit: 25 # optional, max 100Returns campaign, ad set, and optional ad-level performance breakdown.
get_entity_insights
entity_id: "123456789"
entity_type: "adset" # optional: campaign, adset, ad
days: 7 # optional, max 90Returns metadata plus metrics for any entity. Auto-resolves entity type if not provided.
pause_underperforming_entity
entity_id: "123456789"
entity_type: "ad" # optionalSets the entity status to PAUSED.
update_budget
entity_id: "123456789"
new_daily_budget: 50.00 # in account currency (e.g., USD, EUR)
entity_type: "adset" # optional: campaign or adsetBudget is expressed in major currency units. The server converts to minor units for Meta.
create_campaign
campaign_name: "My Campaign"
adset_name: "My Ad Set"
ad_name: "My Ad"
daily_budget: 20.00
destination_url: "https://example.com"
page_id: "123456789"
image_url: "https://example.com/image.jpg"
primary_text: "Your ad copy here"
headline: "Your headline"
description: "Optional description" # optional
instagram_actor_id: "123456789" # optional
url_tags: "utm_source=meta&utm_medium=paid" # optional
call_to_action_type: "SIGN_UP" # optional
pixel_id: "123456789" # optional
objective: "OUTCOME_TRAFFIC" # OUTCOME_TRAFFIC | OUTCOME_LEADS | OUTCOME_SALES | OUTCOME_ENGAGEMENT
countries: ["US", "CA"]
age_min: 25
age_max: 55Every object is created in PAUSED state. Nothing goes live without manual activation.
Additional behavior:
instagram_actor_idis optional. Omit it if the ad account does not have Instagram permissions and create a Facebook-only creative first.url_tagsare sent at the creative level.age_maxcan be constrained by Meta when Advantage+ audience rules apply.
Doctor command
Run a read-only health check without starting the MCP server:
npm run doctorReturns account info, last 7 days metrics, and a sample of active campaigns.
Development
npm run dev # watch mode with tsx
npm run typecheck # type-check without emitting
npm test # run offline testsProject structure
src/
index.ts # Entry point, MCP server + doctor command
config.ts # .env validation with Zod
meta-api.ts # Meta Graph API client
tools.ts # MCP tool registration + input validation
diagnostics.ts # Read-only health report
logger.ts # Secret redaction for logsSecurity
- Credentials loaded exclusively from
.env-- never hardcoded. - Access tokens, secrets, and proofs are redacted in all log output.
- Error messages to the LLM are clear but never expose stack traces or internal state.
appSecretProofis computed via HMAC-SHA256 for every API request.- All campaign creation is forced to
PAUSED-- nothing goes live by default.
Contributing
- Fork the repo
- Create a feature branch (
git checkout -b feature/my-feature) - Make your changes
- Run
npm run typecheck && npm test - Open a pull request
License
MIT -- 0xfabrica
