# 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 ```bash npm install npm run build ``` ## โš™๏ธ Configuration Create a `.env` file from the example: ```bash cp .env.example .env ``` Required environment variables: - `META_ACCESS_TOKEN`: Your Meta Marketing API access token - `META_APP_ID`: (Optional) Your Facebook App ID for enhanced security - `META_APP_SECRET`: (Optional) Your App Secret for appsecret_proof - `META_API_VERSION`: (Optional) API version, defaults to v21.0 ### Getting Access Tokens 1. **User Access Token**: Via [Facebook Graph API Explorer](https://developers.facebook.com/tools/explorer/) - Select your app - Request permissions: `ads_read`, `ads_management`, `business_management` - Generate token 2. **Long-Lived Token**: Exchange short-lived for long-lived (60 days) - Requires App ID and App Secret - Server can auto-refresh using `AuthManager.refreshToLongLivedToken()` 3. **System User Token**: Recommended for production - Created in Business Manager - Never expires (until revoked) - Most secure option ## ๐Ÿš€ Usage ### Development Mode ```bash npm run dev ``` ### Production Mode ```bash npm run build npm start ``` ### As MCP Server Add to your MCP client configuration (e.g., Claude Desktop): ```json { "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_tools` loads full insights and reporting suite - **Audiences**: Gateway tool `audiences_tools` loads custom audience management - **Budget**: Gateway tool `budget_tools` loads optimization and recommendation tools **How Lazy Loading Works:** 1. Tier 2 modules register a single gateway tool (e.g., `audience_tools`) 2. When called, the gateway loads the full module and re-registers all its tools 3. Client sees: "Module loaded, please retry with specific tool" 4. Subsequent calls use the now-loaded tools directly ### Infrastructure Components #### `AuthManager` (`src/auth/oauth.ts`) - Token validation via `debug_token` endpoint - HMAC-SHA256 `appsecret_proof` generation - 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 limits - `x-business-use-case-usage`: Business-level limits - `x-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()`, `post()`, `delete()` methods - Automatic `appsecret_proof` injection - Cursor-based pagination: `getAllPages()` - 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: ```typescript 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: 1. **App-Level**: Calls per hour across all users 2. **Business Use Case**: Per business, per hour 3. **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 `estimatedTimeToRegainAccess` headers ## ๐Ÿ”’ 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 ```typescript // 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: ```bash 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 - [Meta Marketing API Reference](https://developers.facebook.com/docs/marketing-apis/) - [MCP Protocol Specification](https://modelcontextprotocol.io/) - [Campaign Structure Guide](https://developers.facebook.com/docs/marketing-api/campaign-structure/) ## ๐Ÿค Contributing 1. Follow existing patterns for tool registration 2. Use Zod schemas with `.describe()` on every field 3. Include comprehensive MCP annotations 4. Handle errors gracefully with typed responses 5. Add JSDoc comments for complex logic ## ๐Ÿ“„ License MIT ## ๐Ÿ™ Credits Built for Clawdbot by the Meta Ads MCP team.