Meta Ads MCP Server
A production-grade Model Context Protocol (MCP) server for the Meta Marketing API with intelligent lazy-loading architecture.
🌟 Key Features
- Lazy Loading Architecture: Core tools load immediately, advanced features load on-demand
- Production-Ready: Comprehensive rate limiting, caching, and error handling
- Type-Safe: Full TypeScript with strict mode and Zod validation
- MCP Annotations: All tools include proper metadata (readOnly, destructive, idempotent hints)
- Smart Rate Limiting: Tracks Meta's three-tier rate limits with exponential backoff
- Token Management: Automatic validation, expiry tracking, and refresh support
📦 Installation
npm install
npm run build
⚙️ Configuration
Create a .env file from the example:
cp .env.example .env
Required environment variables:
META_ACCESS_TOKEN: Your Meta Marketing API access tokenMETA_APP_ID: (Optional) Your Facebook App ID for enhanced securityMETA_APP_SECRET: (Optional) Your App Secret for appsecret_proofMETA_API_VERSION: (Optional) API version, defaults to v21.0
Getting Access Tokens
-
User Access Token: Via Facebook Graph API Explorer
- Select your app
- Request permissions:
ads_read,ads_management,business_management - Generate token
-
Long-Lived Token: Exchange short-lived for long-lived (60 days)
- Requires App ID and App Secret
- Server can auto-refresh using
AuthManager.refreshToLongLivedToken()
-
System User Token: Recommended for production
- Created in Business Manager
- Never expires (until revoked)
- Most secure option
🚀 Usage
Development Mode
npm run dev
Production Mode
npm run build
npm start
As MCP Server
Add to your MCP client configuration (e.g., Claude Desktop):
{
"mcpServers": {
"meta-ads": {
"command": "node",
"args": ["/path/to/meta-ads-mcp/dist/index.js"],
"env": {
"META_ACCESS_TOKEN": "your_token_here"
}
}
}
}
🏗️ Architecture
Lazy Loading System
The server uses a two-tier module loading strategy:
Tier 1: Core Modules (Load on Startup)
- Account:
list_ad_accounts,get_account_info,get_account_pages,get_business_info,get_pixel_list - Campaigns:
list_campaigns,get_campaign,create_campaign,update_campaign,toggle_campaign,duplicate_campaign,delete_campaign - Ad Sets:
list_ad_sets,get_ad_set,create_ad_set,update_targeting,update_ad_set_budget,update_schedule,toggle_ad_set - Ads:
list_ads,get_ad,create_image_ad,create_video_ad,create_carousel_ad,create_dynamic_ad,preview_creative,upload_media,list_creatives
Tier 2: Advanced Modules (Lazy Loaded)
- Analytics: Gateway tool
analytics_toolsloads full insights and reporting suite - Audiences: Gateway tool
audiences_toolsloads custom audience management - Budget: Gateway tool
budget_toolsloads optimization and recommendation tools
How Lazy Loading Works:
- Tier 2 modules register a single gateway tool (e.g.,
audience_tools) - When called, the gateway loads the full module and re-registers all its tools
- Client sees: "Module loaded, please retry with specific tool"
- Subsequent calls use the now-loaded tools directly
Infrastructure Components
AuthManager (src/auth/oauth.ts)
- Token validation via
debug_tokenendpoint - HMAC-SHA256
appsecret_proofgeneration - Token expiry tracking
- Long-lived token refresh
RateLimiter (src/client/rate-limiter.ts)
- Parses Meta's three rate limit headers:
x-app-usage: App-level limitsx-business-use-case-usage: Business-level limitsx-ad-account-usage: Account-level limits (200 calls/hour)
- Exponential backoff at 75% and 90% thresholds
- Request queue with priority system
- Configurable concurrency (default: 5)
MetaApiClient (src/client/meta-client.ts)
- Generic
get<T>(),post<T>(),delete<T>()methods - Automatic
appsecret_proofinjection - Cursor-based pagination:
getAllPages<T>() - Typed error handling with
MetaApiError - Media upload support (images and videos)
SimpleCache (src/client/cache.ts)
- In-memory TTL-based caching
- Prefix-based invalidation
getOrCompute()for cache-aside pattern- Max entries enforcement (LRU-style)
🛠️ Tool Annotations
Every tool includes MCP annotations:
annotations: {
title: "Human-readable name",
readOnlyHint: true, // No state changes
destructiveHint: false, // Can't be undone
idempotentHint: true, // Safe to retry
openWorldHint: false, // Doesn't access external data
}
Tool Categories
Read-Only Tools: All list/get operations Idempotent Tools: Updates, toggles (safe to retry) Non-Destructive Creates: Campaign/ad set/ad creation Destructive Tools: Delete operations (require confirmation)
📊 Rate Limiting
Meta enforces three types of rate limits:
- App-Level: Calls per hour across all users
- Business Use Case: Per business, per hour
- Ad Account: 200 calls/hour per account ⚠️ Most restrictive
The RateLimiter automatically:
- Monitors all three metrics
- Throttles at 75% usage
- Pauses at 90% usage
- Applies exponential backoff
- Respects
estimatedTimeToRegainAccessheaders
🔒 Security
- appsecret_proof: HMAC signature on every request (when app secret provided)
- Token Validation: Checks token validity on startup
- No Token Logging: Credentials never logged
- .env Support: Secrets in environment, not code
📝 TypeScript
Strict mode enabled with:
- No implicit
any - Strict null checks
- Unused variable detection
- Comprehensive type definitions in
src/types/meta-api.ts
🧪 Example Workflows
Create a Campaign with Ad Set and Image Ad
// 1. List accounts
list_ad_accounts()
// 2. Create campaign
create_campaign({
account_id: "act_123456",
name: "Q1 2025 Campaign",
objective: "OUTCOME_TRAFFIC",
daily_budget: "5000", // $50
status: "PAUSED"
})
// 3. Create ad set
create_ad_set({
campaign_id: "123456789",
name: "US Desktop Traffic",
optimization_goal: "LINK_CLICKS",
billing_event: "IMPRESSIONS",
daily_budget: "2000", // $20
targeting: {
age_min: 25,
age_max: 55,
genders: [1, 2],
geo_locations: { countries: ["US"] },
publisher_platforms: ["facebook"],
device_platforms: ["desktop"]
}
})
// 4. Upload image (stub - needs file handling)
upload_media({
account_id: "act_123456",
file_path: "/path/to/image.jpg",
media_type: "image"
})
// 5. Create ad
create_image_ad({
account_id: "act_123456",
adset_id: "987654321",
name: "Homepage Ad",
page_id: "111222333",
image_hash: "abc123hash",
link: "https://example.com",
headline: "Shop Now",
message: "Limited time offer!",
call_to_action_type: "SHOP_NOW",
status: "ACTIVE"
})
🐛 Debugging
Set NODE_ENV=development for verbose logging:
NODE_ENV=development npm run dev
Logs go to stderr (MCP convention):
[MCP] Module loaded: campaigns[MCP] Registered tool: create_campaign- Rate limit warnings appear automatically
📚 API Documentation
🤝 Contributing
- Follow existing patterns for tool registration
- Use Zod schemas with
.describe()on every field - Include comprehensive MCP annotations
- Handle errors gracefully with typed responses
- Add JSDoc comments for complex logic
📄 License
MIT
🙏 Credits
Built for Clawdbot by the Meta Ads MCP team.